bartenbach / buttbutt

A Java IRC bot inspired by ButtBot with various other functionality
GNU General Public License v3.0
3 stars 1 forks source link

Fails to connect to Mariadb #24

Open BullShark opened 2 years ago

BullShark commented 2 years ago

image

Maybe this can help. https://stackoverflow.com/questions/37909487/how-can-i-connect-to-mariadb-using-java

BullShark commented 2 years ago

I got it working. Here's the files I edited to get it working.

Bot:
    Name: buttbutt
    Nickname: butt
    Login: buttbutt
    Realname: butt
    Password:
    Message-Delay: 0
    No-Verify: false
    Random-Response-Frequency: 100
Channels:
    - '##blackhats-alt'
Server:
    Hostname: 'irc.libera.chat'
    SSL: false
    Port: 6667
    Auto-Reconnect: true
SQL:
    Username: root
    Password: secret
    IP: '[::1]'
    Port: 3306
    Database: irc
    Table-Prefix: ircbutt
package net.alureon.ircbutt.sql;

import net.alureon.ircbutt.IRCbutt;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.sql.*;
import java.util.logging.Level;
//import java.util.logging.LogManager;
//import java.util.logging.Logger;

/**
 * SqlManager contains methods for working with the SQL database on
 * a more general level.  This class gets connections, creates tables,
 * handles disconnections, and houses other convenience methods for
 * working with the bot's SQL database.
 *
 * Note: The temptation to make methods in this class static is overwhelming.
 * I've done it time and time again, only to realize that it's a bad idea.
 * This class represents an INSTANCE of a connection to a database.  It can't be
 * static!  PreparedStatement objects REQUIRE a database connection to be made,
 * therefore these methods cannot be static.
 */
public final class SqlManager {

    /**
     * The Connection object representing a connection to our database.
     */
    private Connection connection;
    /**
     * The IRCbutt instance, for getting configuration file values.
     */
    private IRCbutt butt;
    /**
     * The IP address of the MySQL server.
     */
    private String ip;
    /**
     * The username we connect to the MySQL server with.
     */
    private String username;
    /**
     * The password we connect to the MySQL server with.
     */
    private String password;
    /**
     * The port we connect to the MySQL server with.
     */
    private int port;
    /**
     * The database we will be using in MySQL.
     */
    private String database;
    /**
     * The logger for this class.
     */
    private static final Logger log = LogManager.getLogger();
    /**
     * The SQL timeout value, in seconds, in which, if exceeded a connection is no longer considered valid.
     */
    private static final int SQL_WAIT_TIME = 10;

    /**
     * Constructor for the SqlManager class.
     * @param butt The singleton IRCbutt object.
     */
    public SqlManager(final IRCbutt butt) {
        this.butt = butt;
        this.ip = butt.getYamlConfigurationFile().getSqlIp();
        this.username = butt.getYamlConfigurationFile().getSqlUsername();
        this.password = butt.getYamlConfigurationFile().getSqlPassword();
        this.port = butt.getYamlConfigurationFile().getSqlPort();
        this.database = butt.getYamlConfigurationFile().getSqlDatabase();
    }

    /**
     * This method connects the the database using the information supplied in
     * the YAML configuration file for the bot.
     */
    public void connectToDatabase() {
//        String url = "jdbc:mysql://" + this.ip + ":" + this.port + "/" + this.database + "?autoreconnect=true";
        String url = "jdbc:mariadb://" + this.ip + ":" + this.port + "/" + this.database;
        log.debug(url);
        try {
            Class.forName("org.mariadb.jdbc.Driver");
            connection = DriverManager.getConnection(url, this.username, this.password);
        } catch (SQLException ex) {
            log.error("Failed to establish SQL connection: ", ex.getMessage());
            return;
        }   catch (ClassNotFoundException ex) {
            java.util.logging.Logger.getLogger(SqlManager.class.getName()).log(Level.SEVERE, null, ex);
        }
        log.info("[SQL backend connected]");
    }

    /**
     * Creates the database if it doesn't exist with UTF-8 character set.
     */
    public void createDatabaseIfNotExists() {
        boolean result = sqlUpdate("CREATE DATABASE IF NOT EXISTS ircbutt CHARACTER SET utf8;");
        if (!result) {
            log.fatal("Failed to create database! Shutting down...");
            System.exit(1);
        }
    }

    /**
     * Creates the necessary SQL tables the bot needs.  Requires access to the
     * IRCbutt object to get custom table prefixes.
     */
    public void createTablesIfNotExists() {
        boolean table1Status = sqlUpdate("CREATE TABLE IF NOT EXISTS `"
                + butt.getYamlConfigurationFile().getSqlTablePrefix() + "_quotes` "
                + "(`id` SMALLINT PRIMARY KEY NOT NULL AUTO_INCREMENT, `user` VARCHAR(16) NOT NULL,"
                + "`quote` VARCHAR(500) NOT NULL, `grabbed_by` VARCHAR(16) NOT NULL,"
                + "`timestamp` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP) ENGINE=MyISAM DEFAULT CHARSET=utf8");
        boolean table2Status = sqlUpdate("CREATE TABLE IF NOT EXISTS `"
                + butt.getYamlConfigurationFile().getSqlTablePrefix() + "_knowledge` "
                + "(`id` SMALLINT PRIMARY KEY NOT NULL AUTO_INCREMENT, `item` VARCHAR(32) NOT NULL UNIQUE,"
                + "`data` VARCHAR(500) NOT NULL, `added_by` VARCHAR(16) NOT NULL,"
                + "`timestamp` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP) ENGINE=MyISAM DEFAULT CHARSET=utf8");
        boolean table3Status = sqlUpdate("CREATE TABLE IF NOT EXISTS `"
                + butt.getYamlConfigurationFile().getSqlTablePrefix() + "_karma` "
                + "(`id` SMALLINT PRIMARY KEY NOT NULL AUTO_INCREMENT, `item` VARCHAR(32) NOT NULL,"
                + "`karma` SMALLINT NOT NULL) ENGINE=MyISAM DEFAULT CHARSET=utf8");
        if (!table1Status || !table2Status || !table3Status) {
            log.fatal("Failed to create table in database! Shutting down...");
            System.exit(1);
        }
    }

    /**
     * TODO result of this function is ignored above.
     * Tests for SQL database connection and then executes the passed update
     * statement.
     * @param sql The sql string to execute (not user supplied!).
     * @return True if the update succeeded, false if exception was thrown.
     */
    private boolean sqlUpdate(final String sql) {
        checkConnection();
        try (PreparedStatement ps = connection.prepareStatement(sql)) {
            ps.executeUpdate();
            return true;
        } catch (SQLException ex) {
            log.error("Unable to update SQL database: ", ex.getMessage());
        }
        return false;
    }

    /**
     * Attempts to reconnect to the SQL Database.
     */
    private void reconnect() {
        log.debug("Disconnected from SQL Database. Reconnecting...");
        connectToDatabase();
    }

    /**
     * Returns a PreparedStatement object from the passed String.
     * @param query The query to create into a PreparedStatement.
     * @return The PreparedStatement object.
     */
    public PreparedStatement getPreparedStatement(final String query) {
        checkConnection();
        try {
            return connection.prepareStatement(query);
        } catch (SQLException | NullPointerException ex) {
            log.error("Unable to prepare SQL statement: ", ex.getMessage());
            return null;
        }
    }

    /**
     * This method prepares a statement regardless of the type of data.  I thought this
     * was rather clever.  If the object is of type String, the function will set all of
     * the data passed in, into the appropriate slots of the PreparedStatement object.
     * The same will happen if the passed data happens to be of type Integer.
     * @param ps The PreparedStatement to populate with data
     * @param objects An array of String or Integer objects to put into the PreparedStatement.
     */
    public void prepareStatement(final PreparedStatement ps, final Object... objects) {
        checkConnection();
        try {
            for (int i = 0; i < objects.length; i++) {
                if (objects[i] instanceof String) {
                    ps.setString((i + 1), (String) objects[i]);
                } else {
                    ps.setInt((i + 1), Integer.parseInt(String.valueOf(objects[i])));
                }
            }
        } catch (SQLException ex) {
            log.error("Failed to set parameter in PreparedStatement: ", ex.getMessage());
        }
    }

    /**
     * Executes a query using the passed PreparedStatement and returns the
     * resulting ResultSet object. Convenience method.
     * @param ps The PreparedStatement object to execute query on.
     * @return The resulting ResultSet object.
     */
    public ResultSet getResultSet(final PreparedStatement ps) {
        checkConnection();
        try {
            return ps.executeQuery();
        } catch (SQLException ex) {
            log.error("Failed to execute query from PreparedStatement: ", ex.getMessage());
            return null;
        }
    }

    /**
     * Tests whether or not we are connected to the SQL database.
     * @return True if connected, false if not.
     */
    private boolean isConnected() {
        try {
            return this.connection.isValid(SQL_WAIT_TIME);
        } catch (SQLException ex) {
            log.warn("Exception checking connection validity: ", ex.getMessage());
            return false;
        }
    }

    /**
     * Convenience method that checks SQL database connection and attempts to
     * reconnect if not connected.
     */
    private void checkConnection() {
        if (!isConnected()) {
            reconnect();
        }
    }

}
plugins {
  id 'io.spring.dependency-management' version '1.0.1.RELEASE'
  // this spotbugs plugin would be great if it didn't fail the build over system.exit
  //  id "com.github.spotbugs" version "1.6.0"
}

apply plugin: 'java'
apply plugin: 'idea'
//apply plugin: 'checkstyle'
apply plugin: 'pmd'
apply plugin: 'jacoco'

dependencyManagement {
  imports {
    mavenBom 'org.apache.logging.log4j:log4j-bom:2.10.0' // keeps log4j versions in sync
  }
}

repositories {
    mavenCentral()
}

configurations {
    provided
    compile.extendsFrom provided
}

dependencies {
//    testCompile 'junit:junit:4.12-beta-3' // for junit tests
//    testCompile 'org.junit:junit-gradle:5.0.0-ALPHA' // how do we transition to this?
//    testCompile "org.mockito:mockito-core:+" // for mocking objects when testing
    //yes, we need 3 jars for logging.  don't ask me about it.  I don't want to talk about it.
    implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.10.0'
    implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.10.0'
    implementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.10.0'
    implementation 'org.reflections:reflections:0.9.11' // reflections API for interfaces
    implementation 'org.pircbotx:pircbotx:2.1' // the irc bot API
    implementation 'org.yaml:snakeyaml:1.14' // for the yaml config file
    implementation 'mysql:mysql-connector-java:5.1.45' // for mysql connection
    implementation 'org.jsoup:jsoup:1.11.2' // for web scraping
    implementation 'net.sourceforge.jeval:jeval:0.9.4' // for math operations
    implementation 'com.google.code.gson:gson:2.8.2' // for cryptocurrency package
    implementation 'org.apache.commons:commons-lang3:3.7' // for padding strings
    // https://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client
    implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.4.1'
}

sourceCompatibility = 1.8
version = '0.7.5-indev'

jar {
    dependsOn configurations.runtimeClasspath
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
    from {
        (configurations.runtimeClasspath - configurations.provided).collect {
            it.isDirectory() ? it : zipTree(it)
        }
    } {
//        exclude "META-INF/*.SF"
//        exclude "META-INF/*.DSA"
//        exclude "META-INF/*.RSA"
    }
    manifest {
        attributes 'Implementation-Title': 'IRCbutt',
                'Implementation-Version': version,
                'Implementation-Vendor': 'http://github.com/proxa/IRCbutt',
            'Multi-Release': 'true',
                'Main-Class': 'net.alureon.ircbutt.Main',
                'Built-By': System.properties['user.name']
    }
}
// This bug tracks the warning "warning: No processor claimed any of these annotations: org.junit.Test"
//   which is caused by log4j2
// https://issues.apache.org/jira/browse/LOG4J2-1937
tasks.withType(JavaCompile) {
    options.compilerArgs += ['-Xlint:all']
    options.deprecation = true
    options.encoding = 'utf8'
}

//tasks.withType(Checkstyle) {
//    configFile = new File('config/checkstyle/sane_checks.xml')
//}

// more informative tests
test {
    testLogging {
        events "passed", "skipped", "failed", "standardOut", "standardError"
    }
}
bartenbach commented 2 years ago

Interesting. Well, not really, since this is so outdated, but awesome job getting it working! I'm sure there are a lot of other little bugs lurking around due to bit rot.

I'd be curious to see a diff of those files to see how much you had to change exactly.

BullShark commented 2 years ago
diff --git a/build.gradle b/build.gradle
old mode 100644
new mode 100755
index 1f78c93..d8918d5
--- a/build.gradle
+++ b/build.gradle
@@ -6,7 +6,7 @@ plugins {

apply plugin: 'java'
apply plugin: 'idea'
-apply plugin: 'checkstyle'
+//apply plugin: 'checkstyle'
apply plugin: 'pmd'
apply plugin: 'jacoco'

@@ -26,41 +26,45 @@ configurations {
}

dependencies {
-    testCompile 'junit:junit:4.12-beta-3' // for junit tests
+//    testCompile 'junit:junit:4.12-beta-3' // for junit tests
//    testCompile 'org.junit:junit-gradle:5.0.0-ALPHA' // how do we transition to this?
-    testCompile "org.mockito:mockito-core:+" // for mocking objects when testing
+//    testCompile "org.mockito:mockito-core:+" // for mocking objects when testing
//yes, we need 3 jars for logging.  don't ask me about it.  I don't want to talk about it.
-    compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.10.0'
-    compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.10.0'
-    compile group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.10.0'
-    compile 'org.reflections:reflections:0.9.11' // reflections API for interfaces
-    compile 'org.pircbotx:pircbotx:2.1' // the irc bot API
-    compile 'org.yaml:snakeyaml:1.14' // for the yaml config file
-    compile 'mysql:mysql-connector-java:5.1.45' // for mysql connection
-    compile 'org.jsoup:jsoup:1.11.2' // for web scraping
-    compile 'net.sourceforge.jeval:jeval:0.9.4' // for math operations
-    compile 'com.google.code.gson:gson:2.8.2' // for cryptocurrency package
-    compile 'org.apache.commons:commons-lang3:3.7' // for padding strings
+    implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.10.0'
+    implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.10.0'
+    implementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.10.0'
+    implementation 'org.reflections:reflections:0.9.11' // reflections API for interfaces
+    implementation 'org.pircbotx:pircbotx:2.1' // the irc bot API
+    implementation 'org.yaml:snakeyaml:1.14' // for the yaml config file
+    implementation 'mysql:mysql-connector-java:5.1.45' // for mysql connection
+    implementation 'org.jsoup:jsoup:1.11.2' // for web scraping
+    implementation 'net.sourceforge.jeval:jeval:0.9.4' // for math operations
+    implementation 'com.google.code.gson:gson:2.8.2' // for cryptocurrency package
+    implementation 'org.apache.commons:commons-lang3:3.7' // for padding strings
+    // https://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client
+    implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.4.1'
}

sourceCompatibility = 1.8
version = '0.7.5-indev'

jar {
-    dependsOn configurations.runtime
+    dependsOn configurations.runtimeClasspath
+    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
from {
-        (configurations.runtime - configurations.provided).collect {
+        (configurations.runtimeClasspath - configurations.provided).collect {
it.isDirectory() ? it : zipTree(it)
}
} {
-        exclude "META-INF/*.SF"
-        exclude "META-INF/*.DSA"
-        exclude "META-INF/*.RSA"
+//        exclude "META-INF/*.SF"
+//        exclude "META-INF/*.DSA"
+//        exclude "META-INF/*.RSA"
}
manifest {
attributes 'Implementation-Title': 'IRCbutt',
'Implementation-Version': version,
'Implementation-Vendor': 'http://github.com/proxa/IRCbutt',
+               'Multi-Release': 'true',
'Main-Class': 'net.alureon.ircbutt.Main',
'Built-By': System.properties['user.name']
}
@@ -74,9 +78,9 @@ tasks.withType(JavaCompile) {
options.encoding = 'utf8'
}

-tasks.withType(Checkstyle) {
-    configFile = new File('config/checkstyle/sane_checks.xml')
-}
+//tasks.withType(Checkstyle) {
+//    configFile = new File('config/checkstyle/sane_checks.xml')
+//}

// more informative tests
test {
BullShark commented 2 years ago
diff --git a/pom.xml b/pom.xml
old mode 100644
new mode 100755
index bd9d270..303d8c1
--- a/pom.xml
+++ b/pom.xml
@@ -81,18 +81,19 @@
<version>2.17.0</version>
<scope>compile</scope>
</dependency>
+
+<dependency>
+<groupId>org.apache.logging.log4j</groupId>
+<artifactId>log4j-slf4j18-impl</artifactId>
+<version>2.12.1</version>
+</dependency>
+
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.0</version>
<scope>compile</scope>
</dependency>
-    <dependency>
-      <groupId>org.apache.logging.log4j</groupId>
-      <artifactId>log4j-slf4j-impl</artifactId>
-      <version>2.10.0</version>
-      <scope>compile</scope>
-    </dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
(END)
BullShark commented 2 years ago

image

BullShark commented 2 years ago

Here's how I ran it.

cd NetBeansProjects/buttbutt gradle jar java -jar build/libs/buttbutt-0.7.5-indev.jar

BullShark commented 2 years ago

image

bartenbach commented 2 years ago

Interesting. I used Mariadb back in the day as well, but the string "mysql" worked.

BullShark commented 2 years ago

You can do a runtime check to determine which database is installed and running and then set the connection string specific to that host.