AltitudeWeb/database/src/main/java/com/alttd/altitudeweb/setup/Connection.java

191 lines
8.1 KiB
Java

package com.alttd.altitudeweb.setup;
import com.alttd.altitudeweb.database.Databases;
import com.alttd.altitudeweb.database.web_db.DatabaseSettings;
import com.alttd.altitudeweb.database.web_db.SettingsMapper;
import com.alttd.altitudeweb.type_handler.UUIDTypeHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.function.Consumer;
@Slf4j
public class Connection {
private static final HashMap<Databases, Connection> connections = new HashMap<>();
private SqlSessionFactory sqlSessionFactory;
private final DatabaseSettings settings;
private final AddMappers addMappers;
private Connection(DatabaseSettings settings, AddMappers addMappers) {
this.settings = settings;
this.addMappers = addMappers;
}
public static void initDatabases() {
InitializeWebDb.init();
InitializeLiteBans.init();
InitializeLuckPerms.init();
InitializeProxyPlaytime.init();
InitializeDiscord.init();
InitializeVotingPlugin.init();
}
@FunctionalInterface
public interface AddMappers {
void apply(Configuration configuration);
}
public static Connection getConnection(Databases database) {
if (connections.containsKey(database)) {
return connections.get(database);
}
throw new RuntimeException("Database " + database + " has not been initialized");
}
protected static CompletableFuture<Connection> getConnection(Databases database, AddMappers addMappers) {
if (connections.containsKey(database)) {
return CompletableFuture.completedFuture(connections.get(database));
}
if (database == Databases.DEFAULT) {
return loadDefaultDatabase(addMappers);
}
log.debug("Loading settings for database {}", database.getInternalName());
CompletableFuture<DatabaseSettings> settingsFuture = new CompletableFuture<>();
getConnection(Databases.DEFAULT, (mapper -> mapper.addMapper(SettingsMapper.class))).thenApply(connection -> {
connection.runQuery(session -> {
try {
log.debug("Running query to load settings for database {}", database.getInternalName());
DatabaseSettings loadedSettings = session.getMapper(SettingsMapper.class).getSettings(database.getInternalName());
if (loadedSettings == null) {
log.error("Failed to load settings for database {}. No settings found in db_connection_settings table.",
database.getInternalName());
settingsFuture.completeExceptionally(new IllegalStateException(
"Database settings for " + database.getInternalName() + " not found in db_connection_settings table"));
} else {
log.debug("Loaded settings for database {}: host={}, port={}, name={}",
database.getInternalName(), loadedSettings.host(), loadedSettings.port(), loadedSettings.name());
settingsFuture.complete(loadedSettings);
}
} catch (Exception e) {
log.error("Error occurred while loading database settings for {}", database.getInternalName(), e);
settingsFuture.completeExceptionally(e);
}
});
return null;
}).exceptionally(ex -> {
log.error("Failed to access DEFAULT database to load settings for {}", database.getInternalName(), ex);
settingsFuture.completeExceptionally(ex);
return null;
});
return settingsFuture.thenApply(loadedSettings -> {
log.debug("Storing connection for database {}", database.getInternalName());
Connection connection = new Connection(loadedSettings, addMappers);
connections.put(database, connection);
return connection;
}).exceptionally(ex -> {
log.error("Failed to create connection for database {}", database.getInternalName(), ex);
throw new CompletionException("Failed to initialize database connection for " + database.getInternalName(), ex);
});
}
private static CompletableFuture<Connection> loadDefaultDatabase(AddMappers addMappers) {
DatabaseSettings databaseSettings = new DatabaseSettings(
System.getenv("DB_HOST"),
Integer.parseInt(System.getenv("DB_PORT")),
System.getenv("DB_NAME"),
System.getenv("DB_USER"),
System.getenv("DB_PASS")
);
log.debug("Loaded default database settings {}", databaseSettings);
Connection connection = new Connection(databaseSettings, addMappers);
log.debug("Created default database connection {}", connection);
connections.put(Databases.DEFAULT, connection);
return CompletableFuture.completedFuture(connection);
}
public void runQuery(Consumer<SqlSession> consumer) {
new Thread(() -> {
if (sqlSessionFactory == null) {
sqlSessionFactory = createSqlSessionFactory(settings, addMappers);
}
SqlSession session = null;
try {
session = sqlSessionFactory.openSession();
consumer.accept(session);
session.commit();
} catch (Exception e) {
if (session != null) {
session.rollback();
}
log.error("Failed to run query", e);
} finally {
if (session != null) {
session.close();
}
}
}).start();
}
private SqlSessionFactory createSqlSessionFactory(DatabaseSettings settings, AddMappers addMappers) {
try {
Configuration configuration = getConfiguration(settings);
configuration.getTypeHandlerRegistry().register(UUID.class, UUIDTypeHandler.class);
addMappers.apply(configuration);
return new SqlSessionFactoryBuilder().build(configuration);
} catch (Exception e) {
log.error("""
Failed to create sql session factory with
\thost {}
\tport: {}
\tname: {}
\tusername: {}
""", settings.host(), settings.port(), settings.name(), settings.username(), e);
throw e;
}
}
private static @NotNull Configuration getConfiguration(DatabaseSettings settings) {
PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver("com.mysql.cj.jdbc.Driver");
String url = String.format(
"jdbc:mysql://%s:%d/%s?useSSL=true&tcpKeepAlive=true&socketTimeout=60000&connectTimeout=10000&autoReconnect=false&useUnicode=true&characterEncoding=utf8",
settings.host(),
settings.port(),
settings.name()
);
dataSource.setUrl(url);
dataSource.setUsername(settings.username());
dataSource.setPassword(settings.password());
dataSource.setPoolMaximumActiveConnections(10);
dataSource.setPoolMaximumIdleConnections(5);
dataSource.setPoolTimeToWait(20000);
dataSource.setPoolPingEnabled(true);
dataSource.setPoolPingQuery("SELECT 1");
dataSource.setPoolPingConnectionsNotUsedFor(300000); // 5 min
Environment environment = new Environment("production", new JdbcTransactionFactory(), dataSource);
return new Configuration(environment);
}
}