diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30ab597 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +### IDEA ### +.idea +*.iml + +### MAVEN ### +target/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..8a994ba --- /dev/null +++ b/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + + de.garrus.cloudnet + cloudnet-postgres-database-provider + 1.0 + + + + 1.8 + 1.8 + UTF-8 + + + + + + releases + https://repo.cloudnetservice.eu/repository/releases/ + + + + + snapshots + https://repo.cloudnetservice.eu/repository/snapshots/ + + + + + + garrus-repository + ftp://repo-upload.garrus.de/repo/releases + + + garrus-repository + ftp://repo-upload.garrus.de/repo/shapshot + + + + + + + org.apache.maven.wagon + wagon-ftp + 3.4.1 + + + + + + + + de.dytanic.cloudnet + cloudnet + 3.3.0-RELEASE + provided + + + + + de.dytanic.cloudnet + cloudnet-driver + 3.3.0-RELEASE + provided + + + + + org.postgresql + postgresql + 42.2.12 + + + com.zaxxer + HikariCP + 3.4.2 + + + org.javassist + javassist + 3.27.0-GA + + + + diff --git a/src/main/java/de/garrus/cloudnet/database/postgres/provider/CloudNetPostgresDatabaseModule.java b/src/main/java/de/garrus/cloudnet/database/postgres/provider/CloudNetPostgresDatabaseModule.java new file mode 100644 index 0000000..0ac9a29 --- /dev/null +++ b/src/main/java/de/garrus/cloudnet/database/postgres/provider/CloudNetPostgresDatabaseModule.java @@ -0,0 +1,40 @@ +package de.garrus.cloudnet.database.postgres.provider; + +import de.dytanic.cloudnet.database.AbstractDatabaseProvider; +import de.dytanic.cloudnet.driver.module.ModuleLifeCycle; +import de.dytanic.cloudnet.driver.module.ModuleTask; +import de.dytanic.cloudnet.module.NodeCloudNetModule; + +public class CloudNetPostgresDatabaseModule extends NodeCloudNetModule { + private static CloudNetPostgresDatabaseModule instance; + + public static CloudNetPostgresDatabaseModule getInstance() { + return instance; + } + + @ModuleTask(order = 127, event = ModuleLifeCycle.LOADED) + public void init() { + instance = this; + } + + @ModuleTask(order = 126, event = ModuleLifeCycle.LOADED) + public void initConfig() { + this.getConfig().getString("addresse", "jdbc:postgresql://127.0.0.1:5432/database"); + this.getConfig().getString("username", "root"); + this.getConfig().getString("password", "root"); + this.getConfig().getInt("connectionPoolSize", 15); + this.getConfig().getInt("connectionTimeout", 5000); + this.getConfig().getInt("validationTimeout", 5000); + this.saveConfig(); + } + + @ModuleTask(order = 125, event = ModuleLifeCycle.LOADED) + public void registerDatabaseProvider() { + this.getRegistry().registerService(AbstractDatabaseProvider.class, "postgres", new PostgresSQLDatabaseProvider(getConfig(),null)); + } + + @ModuleTask(order = 127, event = ModuleLifeCycle.STOPPED) + public void unregisterDatabaseProvider() { + this.getRegistry().unregisterService(AbstractDatabaseProvider.class, "postgres"); + } +} diff --git a/src/main/java/de/garrus/cloudnet/database/postgres/provider/PostgresDatabase.java b/src/main/java/de/garrus/cloudnet/database/postgres/provider/PostgresDatabase.java new file mode 100644 index 0000000..13476b5 --- /dev/null +++ b/src/main/java/de/garrus/cloudnet/database/postgres/provider/PostgresDatabase.java @@ -0,0 +1,12 @@ +package de.garrus.cloudnet.database.postgres.provider; + +import de.dytanic.cloudnet.database.sql.SQLDatabase; +import de.dytanic.cloudnet.database.sql.SQLDatabaseProvider; + +import java.util.concurrent.ExecutorService; + +public class PostgresDatabase extends SQLDatabase { + public PostgresDatabase(SQLDatabaseProvider databaseProvider, String name, ExecutorService executorService) { + super(databaseProvider, name, executorService); + } +} diff --git a/src/main/java/de/garrus/cloudnet/database/postgres/provider/PostgresSQLDatabaseProvider.java b/src/main/java/de/garrus/cloudnet/database/postgres/provider/PostgresSQLDatabaseProvider.java new file mode 100644 index 0000000..f7e04a3 --- /dev/null +++ b/src/main/java/de/garrus/cloudnet/database/postgres/provider/PostgresSQLDatabaseProvider.java @@ -0,0 +1,221 @@ +package de.garrus.cloudnet.database.postgres.provider; + +import com.google.common.base.Preconditions; +import com.zaxxer.hikari.HikariDataSource; +import de.dytanic.cloudnet.common.collection.NetorHashMap; +import de.dytanic.cloudnet.common.collection.Pair; +import de.dytanic.cloudnet.common.concurrent.IThrowableCallback; +import de.dytanic.cloudnet.common.document.gson.JsonDocument; +import de.dytanic.cloudnet.database.IDatabase; +import de.dytanic.cloudnet.database.sql.SQLDatabaseProvider; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; + +public class PostgresSQLDatabaseProvider extends SQLDatabaseProvider { + private static final long NEW_CREATION_DELAY = 600000L; + protected final NetorHashMap cachedDatabaseInstances = new NetorHashMap<>(); + protected final HikariDataSource hikariDataSource = new HikariDataSource(); + private final JsonDocument config; + private String address; + + public PostgresSQLDatabaseProvider(JsonDocument config,ExecutorService executorService) { + super(executorService); + this.config = config; + } + + public boolean init() { + this.address = this.config.getString("addresse"); + this.hikariDataSource.setJdbcUrl(address); + this.hikariDataSource.setUsername(this.config.getString("username")); + this.hikariDataSource.setPassword(this.config.getString("password")); + this.hikariDataSource.setDriverClassName("org.postgresql.Driver"); + this.hikariDataSource.setMaximumPoolSize(this.config.getInt("connectionPoolSize")); + this.hikariDataSource.setConnectionTimeout(this.config.getInt("connectionTimeout")); + this.hikariDataSource.setValidationTimeout(this.config.getInt("validationTimeout")); + this.hikariDataSource.validate(); + return true; + } + + public IDatabase getDatabase(String name) { + Preconditions.checkNotNull(name); + this.removedOutdatedEntries(); + if (!this.cachedDatabaseInstances.contains(name)) { + this.cachedDatabaseInstances.add(name, System.currentTimeMillis() + NEW_CREATION_DELAY, new PostgresDatabase(this, name,super.executorService) { + }); + } + + return this.cachedDatabaseInstances.getSecond(name); + } + + public boolean containsDatabase(String name) { + Preconditions.checkNotNull(name); + this.removedOutdatedEntries(); + + return getDatabaseNames().contains(name); + } + + public boolean deleteDatabase(String name) { + Preconditions.checkNotNull(name); + this.cachedDatabaseInstances.remove(name); + if (this.containsDatabase(name)) { + return false; + } + + try { + Connection connection = getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("DROP TABLE " + name); + boolean deleted = preparedStatement.executeUpdate() != -1; + + if (!preparedStatement.isClosed()) { + preparedStatement.close(); + } + + if (!connection.isClosed()) { + connection.close(); + } + + return deleted; + } catch (SQLException throwables) { + throwables.printStackTrace(); + } + + return false; + } + + public List getDatabaseNames() { + return this.executeQuery("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='PUBLIC'", resultSet -> { + List collection = new ArrayList<>(); + + while (resultSet.next()) { + collection.add(resultSet.getString("table_name")); + } + + return collection; + }); + } + + public String getName() { + return this.config.getString("database"); + } + + public void close() { + this.hikariDataSource.close(); + } + + private void removedOutdatedEntries() { + Iterator>> var1 = this.cachedDatabaseInstances.entrySet().iterator(); + + while (var1.hasNext()) { + Map.Entry> entry = var1.next(); + if ((Long) ((Pair) entry.getValue()).getFirst() < System.currentTimeMillis()) { + this.cachedDatabaseInstances.remove(entry.getKey()); + } + } + + } + + public Connection getConnection() throws SQLException { + return this.hikariDataSource.getConnection(); + } + + public NetorHashMap getCachedDatabaseInstances() { + return this.cachedDatabaseInstances; + } + + public HikariDataSource getHikariDataSource() { + return this.hikariDataSource; + } + + public JsonDocument getConfig() { + return this.config; + } + + public String getAddress() { + return this.address; + } + + public int executeUpdate(String query, Object... objects) { + Preconditions.checkNotNull(query); + Preconditions.checkNotNull(objects); + + try { + Connection connection = getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement(query); + + for (int i = 0; i < objects.length; i++) { + preparedStatement.setString(i + 1, objects[i].toString()); + } + int i = preparedStatement.executeUpdate(); + + if (!preparedStatement.isClosed()) { + preparedStatement.close(); + } + if (!connection.isClosed()) { + connection.close(); + } + + return i; + } catch (SQLException throwables) { + throwables.printStackTrace(); + return -1; + } + + } + + public T executeQuery(String query, IThrowableCallback callback, Object... objects) { + Preconditions.checkNotNull(query); + Preconditions.checkNotNull(callback); + Preconditions.checkNotNull(objects); + + try { + Connection connection = getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement(query); + + for (int i = 0; i < objects.length; i++) { + preparedStatement.setString(i + 1, objects[i].toString()); + } + + ResultSet resultSet = preparedStatement.executeQuery(); + + try { + T call = callback.call(resultSet); + + if (!resultSet.isClosed()) { + resultSet.close(); + } + if (!preparedStatement.isClosed()) { + preparedStatement.close(); + } + if (!connection.isClosed()) { + connection.close(); + } + return call; + + } catch (Throwable throwable) { + if (!resultSet.isClosed()) { + resultSet.close(); + } + if (!preparedStatement.isClosed()) { + preparedStatement.close(); + } + if (!connection.isClosed()) { + connection.close(); + } + throwable.printStackTrace(); + return null; + } + } catch (SQLException throwables) { + throwables.printStackTrace(); + return null; + } + + } +} diff --git a/src/main/resources/module.json b/src/main/resources/module.json new file mode 100644 index 0000000..dd85489 --- /dev/null +++ b/src/main/resources/module.json @@ -0,0 +1,43 @@ +{ + "runtimeModule": true, + "group": "de.garrus.cloudnet", + "name": "Cloudnet-Postgres-Database-Provider", + "version": "1.0", + "author": "garrus.de", + "website": "https://garrus.de", + "description": "CloudNet extension, which includes the database support for PostgreSQL database to store", + "main": "de.garrus.cloudnet.database.postgres.provider.CloudNetPostgresDatabaseModule", + "storesSensitiveData": true, + "dependencies": [ + { + "group": "org.postgresql", + "name": "postgresql", + "version": "42.2.12", + "repo": "maven" + }, + { + "group": "com.zaxxer", + "name": "HikariCP", + "version": "3.4.2", + "repo": "maven" + }, + { + "group": "org.javassist", + "name": "javassist", + "version": "3.27.0-GA", + "repo": "maven" + }, + { + "group": "org.slf4j", + "name": "slf4j-api", + "version": "1.7.25", + "repo": "maven" + }, + { + "group": "org.slf4j", + "name": "slf4j-nop", + "version": "1.7.25", + "repo": "maven" + } + ] +}