All database transactions are now performed by a single thread.

- Added a Database Access Lock object
  - Access log lets OperationCriticalTransactions through
  - Transactions skip query access log check
  - executeTransaction returns a Future to allow easier synchronization
  - ServerInfo waits for the server to be registered. This could lead
    to issues if a new server is registering to old database. It should
    not be too big of an issue since no patches need to be applied
    on first enable of the database.
- Added database states: CLOSED <-> INITIALIZING -> OPEN -> CLOSED

These two changes allow restricting queries to the database until the
database has properly initialized (Schema is in correct format)

- Removed SQLDB as a Patch class variable

Tests use Guava direct thread executor on the database to reduce
concurrency issues during tests. Another option would be to wait for
each transaction.
This commit is contained in:
Rsl1122 2019-02-16 20:26:08 +02:00
parent 2b9e407816
commit 8870e034e1
49 changed files with 434 additions and 414 deletions

View File

@ -31,6 +31,7 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
/**
* Manages Server information on the Bungee instance.
@ -70,8 +71,10 @@ public class BungeeServerInfo extends ServerInfo {
} else {
server = registerBungeeInfo(database);
}
} catch (DBOpException e) {
} catch (DBOpException | ExecutionException e) {
throw new EnableException("Failed to read Server information from Database.");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return server;
}
@ -93,12 +96,13 @@ public class BungeeServerInfo extends ServerInfo {
}
}
private Server registerBungeeInfo(Database db) throws EnableException {
private Server registerBungeeInfo(Database db) throws EnableException, ExecutionException, InterruptedException {
UUID serverUUID = generateNewUUID();
String accessAddress = webServer.get().getAccessAddress();
Server proxy = new Server(-1, serverUUID, "BungeeCord", accessAddress, serverProperties.getMaxPlayers());
db.executeTransaction(new StoreServerInformationTransaction(proxy));
db.executeTransaction(new StoreServerInformationTransaction(proxy))
.get();
Optional<Server> proxyInfo = db.query(ServerQueries.fetchProxyServerInformation());
if (proxyInfo.isPresent()) {

View File

@ -17,11 +17,13 @@
package com.djrapitops.plan;
import com.djrapitops.plan.api.exceptions.EnableException;
import com.djrapitops.plan.db.SQLiteDB;
import com.djrapitops.plan.system.PlanSystem;
import com.djrapitops.plan.system.database.DBSystem;
import com.djrapitops.plan.system.settings.config.PlanConfig;
import com.djrapitops.plan.system.settings.paths.ProxySettings;
import com.djrapitops.plan.system.settings.paths.WebserverSettings;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
@ -62,7 +64,9 @@ public class BungeeSystemTest {
config.set(ProxySettings.IP, "8.8.8.8");
DBSystem dbSystem = bungeeSystem.getDatabaseSystem();
dbSystem.setActiveDatabase(dbSystem.getSqLiteFactory().usingDefaultFile());
SQLiteDB db = dbSystem.getSqLiteFactory().usingDefaultFile();
db.setTransactionExecutorServiceProvider(MoreExecutors::newDirectExecutorService);
dbSystem.setActiveDatabase(db);
bungeeSystem.enable();
} finally {
@ -82,7 +86,9 @@ public class BungeeSystemTest {
config.set(ProxySettings.IP, "0.0.0.0");
DBSystem dbSystem = bungeeSystem.getDatabaseSystem();
dbSystem.setActiveDatabase(dbSystem.getSqLiteFactory().usingDefaultFile());
SQLiteDB db = dbSystem.getSqLiteFactory().usingDefaultFile();
db.setTransactionExecutorServiceProvider(MoreExecutors::newDirectExecutorService);
dbSystem.setActiveDatabase(db);
bungeeSystem.enable();
assertTrue(bungeeSystem.isEnabled());

View File

@ -77,7 +77,6 @@ public class ShutdownHook extends Thread {
public void run() {
try {
Map<UUID, Session> activeSessions = SessionCache.getActiveSessions();
long now = System.currentTimeMillis();
prepareSessionsForStorage(activeSessions, System.currentTimeMillis());
saveActiveSessions(activeSessions);
} catch (IllegalStateException ignored) {
@ -91,7 +90,7 @@ public class ShutdownHook extends Thread {
private void saveActiveSessions(Map<UUID, Session> activeSessions) throws DBInitException {
Database database = dbSystem.getDatabase();
if (!database.isOpen()) {
if (database.getState() == Database.State.CLOSED) {
// Ensure that database is not closed when performing the transaction.
database.init();
}

View File

@ -21,7 +21,7 @@ package com.djrapitops.plan.api.exceptions.database;
*
* @author Rsl1122
*/
public class DBInitException extends FatalDBException {
public class DBInitException extends DBException {
public DBInitException(String message, Throwable cause) {
super(message, cause);

View File

@ -16,15 +16,7 @@
*/
package com.djrapitops.plan.api.exceptions.database;
public class FatalDBException extends DBException {
public FatalDBException(String message, Throwable cause) {
super(message, cause);
}
public FatalDBException(Throwable cause) {
super(cause);
}
public class FatalDBException extends DBOpException {
public FatalDBException(String message) {
super(message);

View File

@ -84,7 +84,7 @@ public class ManageHotSwapCommand extends CommandNode {
Database database = dbSystem.getActiveDatabaseByName(dbName);
database.init();
if (!database.isOpen()) {
if (database.getState() == Database.State.CLOSED) {
return;
}
} catch (Exception e) {

View File

@ -25,11 +25,21 @@ package com.djrapitops.plan.db;
*/
public abstract class AbstractDatabase implements Database {
protected volatile boolean open = false;
protected DBAccessLock accessLock;
private State state;
@Override
public boolean isOpen() {
return open;
public AbstractDatabase() {
state = State.CLOSED;
accessLock = new DBAccessLock(this);
}
@Override
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
accessLock.operabilityChanged();
}
}

View File

@ -0,0 +1,73 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.db;
import com.djrapitops.plan.api.exceptions.database.DBOpException;
import com.djrapitops.plan.db.access.transactions.Transaction;
import com.djrapitops.plan.db.access.transactions.init.OperationCriticalTransaction;
/**
* Database Lock that prevents queries and transactions from taking place before database schema is ready.
* <p>
* - OperationCriticalTransactions pass through the Access lock without blocking to allow the initial transactions.
* - Queries inside Transactions skip access log to allow OperationCriticalTransactions perform queries.
*
* @author Rsl1122
*/
public class DBAccessLock {
private final Database database;
private final Object lockObject;
public DBAccessLock(Database database) {
this.database = database;
this.lockObject = new Object();
}
public void checkAccess() {
checkAccess(false);
}
public void checkAccess(Transaction transaction) {
checkAccess(transaction instanceof OperationCriticalTransaction);
}
private void checkAccess(boolean isOperationCriticalTransaction) {
if (isOperationCriticalTransaction) {
return;
}
try {
while (database.getState() != Database.State.OPEN) {
synchronized (lockObject) {
lockObject.wait();
if (database.getState() == Database.State.CLOSED) {
throw new DBOpException("Database failed to open, Query has failed. (This exception is necessary to not keep query threads waiting)");
}
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public void operabilityChanged() {
synchronized (lockObject) {
lockObject.notifyAll();
}
}
}

View File

@ -21,6 +21,8 @@ import com.djrapitops.plan.api.exceptions.database.DBInitException;
import com.djrapitops.plan.db.access.Query;
import com.djrapitops.plan.db.access.transactions.Transaction;
import java.util.concurrent.Future;
/**
* Interface for interacting with a Plan SQL database.
*
@ -32,8 +34,6 @@ public interface Database {
void close() throws DBException;
boolean isOpen();
/**
* Execute an SQL Query statement to get a result.
* <p>
@ -49,8 +49,9 @@ public interface Database {
* Execute an SQL Transaction.
*
* @param transaction Transaction to execute.
* @return Future that is finished when the transaction has been executed.
*/
void executeTransaction(Transaction transaction);
Future<?> executeTransaction(Transaction transaction);
/**
* Used to get the {@code DBType} of the Database
@ -58,9 +59,16 @@ public interface Database {
* @return the {@code DBType}
* @see DBType
*/
@Deprecated
DBType getType();
@Deprecated
void scheduleClean(long delay);
State getState();
enum State {
CLOSED,
INITIALIZING,
OPEN
}
}

View File

@ -25,8 +25,7 @@ import com.djrapitops.plan.system.locale.Locale;
import com.djrapitops.plan.system.settings.config.PlanConfig;
import com.djrapitops.plan.system.settings.paths.DatabaseSettings;
import com.djrapitops.plan.utilities.MiscUtils;
import com.djrapitops.plugin.benchmarking.Timings;
import com.djrapitops.plugin.logging.L;
import com.djrapitops.plan.utilities.java.ThrowableUtils;
import com.djrapitops.plugin.logging.console.PluginLogger;
import com.djrapitops.plugin.logging.error.ErrorHandler;
import com.djrapitops.plugin.task.PluginTask;
@ -61,10 +60,9 @@ public class H2DB extends SQLDB {
NetworkContainer.Factory networkContainerFactory,
RunnableFactory runnableFactory,
PluginLogger logger,
Timings timings,
ErrorHandler errorHandler
) {
super(() -> serverInfo.get().getServerUUID(), locale, config, networkContainerFactory, runnableFactory, logger, timings, errorHandler);
super(() -> serverInfo.get().getServerUUID(), locale, config, networkContainerFactory, runnableFactory, logger, errorHandler);
dbName = databaseFile.getName();
this.databaseFile = databaseFile;
}
@ -77,7 +75,7 @@ public class H2DB extends SQLDB {
throw new DBInitException(e);
}
execute("SET REFERENTIAL_INTEGRITY FALSE");
// TODO Figure out if execute("SET REFERENTIAL_INTEGRITY FALSE"); is required
startConnectionPingTask();
}
@ -145,22 +143,12 @@ public class H2DB extends SQLDB {
stopConnectionPingTask();
if (connection != null) {
logger.debug("H2DB " + dbName + ": Closed Connection");
logger.debug("H2 Connection close prompted by: " + ThrowableUtils.findCallerAfterClass(Thread.currentThread().getStackTrace(), H2DB.class));
logger.debug("H2 " + dbName + ": Closed Connection");
MiscUtils.close(connection);
}
}
@Override
public void commit(Connection connection) {
try {
connection.commit();
} catch (SQLException e) {
if (!e.getMessage().contains("cannot commit")) {
errorHandler.log(L.ERROR, this.getClass(), e);
}
}
}
@Override
public void returnToPool(Connection connection) {
// Connection pool not in use, no action required.
@ -189,7 +177,6 @@ public class H2DB extends SQLDB {
private final NetworkContainer.Factory networkContainerFactory;
private final RunnableFactory runnableFactory;
private final PluginLogger logger;
private final Timings timings;
private final ErrorHandler errorHandler;
private PlanFiles files;
@ -202,7 +189,6 @@ public class H2DB extends SQLDB {
NetworkContainer.Factory networkContainerFactory,
RunnableFactory runnableFactory,
PluginLogger logger,
Timings timings,
ErrorHandler errorHandler
) {
this.locale = locale;
@ -212,7 +198,6 @@ public class H2DB extends SQLDB {
this.networkContainerFactory = networkContainerFactory;
this.runnableFactory = runnableFactory;
this.logger = logger;
this.timings = timings;
this.errorHandler = errorHandler;
}
@ -228,7 +213,7 @@ public class H2DB extends SQLDB {
return new H2DB(databaseFile,
locale, config, serverInfo,
networkContainerFactory,
runnableFactory, logger, timings, errorHandler
runnableFactory, logger, errorHandler
);
}

View File

@ -63,7 +63,7 @@ public class MySQLDB extends SQLDB {
Timings timings,
ErrorHandler errorHandler
) {
super(() -> serverInfo.get().getServerUUID(), locale, config, networkContainerFactory, runnableFactory, pluginLogger, timings, errorHandler);
super(() -> serverInfo.get().getServerUUID(), locale, config, networkContainerFactory, runnableFactory, pluginLogger, errorHandler);
}
private static synchronized void increment() {
@ -154,11 +154,6 @@ public class MySQLDB extends SQLDB {
}
}
@Override
public void commit(Connection connection) {
returnToPool(connection);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View File

@ -18,35 +18,35 @@ package com.djrapitops.plan.db;
import com.djrapitops.plan.api.exceptions.database.DBInitException;
import com.djrapitops.plan.api.exceptions.database.DBOpException;
import com.djrapitops.plan.api.exceptions.database.FatalDBException;
import com.djrapitops.plan.data.store.containers.NetworkContainer;
import com.djrapitops.plan.db.access.ExecStatement;
import com.djrapitops.plan.db.access.Query;
import com.djrapitops.plan.db.access.QueryStatement;
import com.djrapitops.plan.db.access.transactions.Transaction;
import com.djrapitops.plan.db.access.transactions.init.CleanTransaction;
import com.djrapitops.plan.db.access.transactions.init.CreateIndexTransaction;
import com.djrapitops.plan.db.access.transactions.init.CreateTablesTransaction;
import com.djrapitops.plan.db.access.transactions.init.OperationCriticalTransaction;
import com.djrapitops.plan.db.patches.*;
import com.djrapitops.plan.system.locale.Locale;
import com.djrapitops.plan.system.settings.config.PlanConfig;
import com.djrapitops.plan.system.settings.paths.PluginSettings;
import com.djrapitops.plan.system.settings.paths.TimeSettings;
import com.djrapitops.plan.utilities.java.ThrowableUtils;
import com.djrapitops.plugin.api.TimeAmount;
import com.djrapitops.plugin.benchmarking.Timings;
import com.djrapitops.plugin.logging.L;
import com.djrapitops.plugin.logging.console.PluginLogger;
import com.djrapitops.plugin.logging.error.ErrorHandler;
import com.djrapitops.plugin.task.AbsRunnable;
import com.djrapitops.plugin.task.PluginTask;
import com.djrapitops.plugin.task.RunnableFactory;
import com.djrapitops.plugin.utilities.Verify;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.*;
import java.util.function.Supplier;
/**
@ -63,18 +63,19 @@ public abstract class SQLDB extends AbstractDatabase {
protected final NetworkContainer.Factory networkContainerFactory;
protected final RunnableFactory runnableFactory;
protected final PluginLogger logger;
protected final Timings timings;
protected final ErrorHandler errorHandler;
private PluginTask dbCleanTask;
private Supplier<ExecutorService> transactionExecutorServiceProvider;
private ExecutorService transactionExecutor;
public SQLDB(
Supplier<UUID> serverUUIDSupplier,
Locale locale,
PlanConfig config,
NetworkContainer.Factory networkContainerFactory, RunnableFactory runnableFactory,
PluginLogger logger,
Timings timings,
ErrorHandler errorHandler
) {
this.serverUUIDSupplier = serverUUIDSupplier;
@ -83,8 +84,9 @@ public abstract class SQLDB extends AbstractDatabase {
this.networkContainerFactory = networkContainerFactory;
this.runnableFactory = runnableFactory;
this.logger = logger;
this.timings = timings;
this.errorHandler = errorHandler;
this.transactionExecutorServiceProvider = () -> Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("Plan " + getClass().getSimpleName() + "-transaction-thread-%d").build());
}
/**
@ -99,13 +101,38 @@ public abstract class SQLDB extends AbstractDatabase {
*/
@Override
public void init() throws DBInitException {
setOpen(true);
List<Runnable> unfinishedTransactions = closeTransactionExecutor(transactionExecutor);
this.transactionExecutor = transactionExecutorServiceProvider.get();
setState(State.INITIALIZING);
setupDataSource();
setupDatabase();
for (Runnable unfinishedTransaction : unfinishedTransactions) {
transactionExecutor.submit(unfinishedTransaction);
}
// If an OperationCriticalTransaction fails open is set to false.
// See executeTransaction method below.
if (getState() == State.CLOSED) {
throw new DBInitException("Failed to set-up Database");
}
}
void setOpen(boolean value) {
open = value;
private List<Runnable> closeTransactionExecutor(ExecutorService transactionExecutor) {
if (transactionExecutor == null || transactionExecutor.isShutdown() || transactionExecutor.isTerminated()) {
return Collections.emptyList();
}
transactionExecutor.shutdown();
try {
if (!transactionExecutor.awaitTermination(5L, TimeUnit.SECONDS)) {
return transactionExecutor.shutdownNow();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return Collections.emptyList();
}
@Override
@ -114,7 +141,7 @@ public abstract class SQLDB extends AbstractDatabase {
@Override
public void run() {
try {
if (isOpen()) {
if (getState() != State.CLOSED) {
executeTransaction(new CleanTransaction(serverUUIDSupplier.get(),
config.get(TimeSettings.KEEP_INACTIVE_PLAYERS), logger, locale)
);
@ -132,27 +159,27 @@ public abstract class SQLDB extends AbstractDatabase {
Patch[] patches() {
return new Patch[]{
new Version10Patch(this),
new GeoInfoLastUsedPatch(this),
new SessionAFKTimePatch(this),
new KillsServerIDPatch(this),
new WorldTimesSeverIDPatch(this),
new WorldsServerIDPatch(this),
new NicknameLastSeenPatch(this),
new VersionTableRemovalPatch(this),
new DiskUsagePatch(this),
new WorldsOptimizationPatch(this),
new WorldTimesOptimizationPatch(this),
new KillsOptimizationPatch(this),
new SessionsOptimizationPatch(this),
new PingOptimizationPatch(this),
new NicknamesOptimizationPatch(this),
new UserInfoOptimizationPatch(this),
new GeoInfoOptimizationPatch(this),
new TransferTableRemovalPatch(this),
new IPHashPatch(this),
new IPAnonPatch(this),
new BadAFKThresholdValuePatch(this)
new Version10Patch(),
new GeoInfoLastUsedPatch(),
new SessionAFKTimePatch(),
new KillsServerIDPatch(),
new WorldTimesSeverIDPatch(),
new WorldsServerIDPatch(),
new NicknameLastSeenPatch(),
new VersionTableRemovalPatch(),
new DiskUsagePatch(),
new WorldsOptimizationPatch(),
new WorldTimesOptimizationPatch(),
new KillsOptimizationPatch(),
new SessionsOptimizationPatch(),
new PingOptimizationPatch(),
new NicknamesOptimizationPatch(),
new UserInfoOptimizationPatch(),
new GeoInfoOptimizationPatch(),
new TransferTableRemovalPatch(),
new IPHashPatch(),
new IPAnonPatch(),
new BadAFKThresholdValuePatch()
};
}
@ -160,19 +187,19 @@ public abstract class SQLDB extends AbstractDatabase {
* Ensures connection functions correctly and all tables exist.
* <p>
* Updates to latest schema.
*
* @throws DBInitException if something goes wrong.
*/
private void setupDatabase() throws DBInitException {
try {
executeTransaction(new CreateTablesTransaction());
for (Patch patch : patches()) {
executeTransaction(patch);
}
registerIndexCreationTask();
} catch (DBOpException | IllegalArgumentException e) {
throw new DBInitException("Failed to set-up Database", e);
private void setupDatabase() {
executeTransaction(new CreateTablesTransaction());
for (Patch patch : patches()) {
executeTransaction(patch);
}
executeTransaction(new OperationCriticalTransaction() {
@Override
protected void performOperations() {
if (getState() == State.INITIALIZING) setState(State.OPEN);
}
});
registerIndexCreationTask();
}
private void registerIndexCreationTask() {
@ -192,7 +219,8 @@ public abstract class SQLDB extends AbstractDatabase {
@Override
public void close() {
setOpen(false);
setState(State.CLOSED);
closeTransactionExecutor(transactionExecutor);
if (dbCleanTask != null) {
dbCleanTask.cancel();
}
@ -200,100 +228,46 @@ public abstract class SQLDB extends AbstractDatabase {
public abstract Connection getConnection() throws SQLException;
@Deprecated
public abstract void commit(Connection connection);
public abstract void returnToPool(Connection connection);
@Deprecated
public boolean execute(ExecStatement statement) {
if (!isOpen()) {
throw new DBOpException("SQL Statement tried to execute while connection closed");
}
Connection connection = null;
try {
connection = getConnection();
try (PreparedStatement preparedStatement = connection.prepareStatement(statement.getSql())) {
return statement.execute(preparedStatement);
}
} catch (SQLException e) {
throw DBOpException.forCause(statement.getSql(), e);
} finally {
commit(connection);
}
}
@Deprecated
public boolean execute(String sql) {
return execute(new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) {
// Statement is ready for execution.
}
});
}
@Deprecated
public void executeUnsafe(String... statements) {
Verify.nullCheck(statements);
for (String statement : statements) {
try {
execute(statement);
} catch (DBOpException e) {
if (config.isTrue(PluginSettings.DEV_MODE)) {
errorHandler.log(L.ERROR, this.getClass(), e);
}
}
}
}
@Deprecated
public void executeBatch(ExecStatement statement) {
if (!isOpen()) {
throw new DBOpException("SQL Batch tried to execute while connection closed");
}
Connection connection = null;
try {
connection = getConnection();
try (PreparedStatement preparedStatement = connection.prepareStatement(statement.getSql())) {
statement.executeBatch(preparedStatement);
}
} catch (SQLException e) {
throw DBOpException.forCause(statement.getSql(), e);
} finally {
commit(connection);
}
}
@Deprecated
public <T> T query(QueryStatement<T> statement) {
if (!isOpen()) {
throw new DBOpException("SQL Query tried to execute while connection closed");
}
Connection connection = null;
try {
connection = getConnection();
try (PreparedStatement preparedStatement = connection.prepareStatement(statement.getSql())) {
return statement.executeQuery(preparedStatement);
}
} catch (SQLException e) {
throw DBOpException.forCause(statement.getSql(), e);
} finally {
returnToPool(connection);
}
}
@Override
public <T> T query(Query<T> query) {
accessLock.checkAccess();
return query.executeQuery(this);
}
@Override
public void executeTransaction(Transaction transaction) {
transaction.executeTransaction(this);
public Future<?> executeTransaction(Transaction transaction) {
if (getState() == State.CLOSED) {
throw new DBOpException("Transaction tried to execute although database is closed.");
}
Exception origin = new Exception();
return CompletableFuture.supplyAsync(() -> {
accessLock.checkAccess(transaction);
transaction.executeTransaction(this);
return CompletableFuture.completedFuture(null);
}, getTransactionExecutor()).handle((obj, throwable) -> {
if (throwable == null) {
return CompletableFuture.completedFuture(null);
}
if (throwable instanceof FatalDBException) {
setState(State.CLOSED);
}
ThrowableUtils.appendEntryPointToCause(throwable, origin);
errorHandler.log(L.ERROR, getClass(), throwable);
return CompletableFuture.completedFuture(null);
});
}
private ExecutorService getTransactionExecutor() {
if (transactionExecutor == null) {
transactionExecutor = transactionExecutorServiceProvider.get();
}
return transactionExecutor;
}
@Override
@ -313,19 +287,11 @@ public abstract class SQLDB extends AbstractDatabase {
return serverUUIDSupplier;
}
public PlanConfig getConfig() {
return config;
}
public PluginLogger getLogger() {
return logger;
}
public Locale getLocale() {
return locale;
}
public NetworkContainer.Factory getNetworkContainerFactory() {
return networkContainerFactory;
}
public void setTransactionExecutorServiceProvider(Supplier<ExecutorService> transactionExecutorServiceProvider) {
this.transactionExecutorServiceProvider = transactionExecutorServiceProvider;
}
}

View File

@ -25,7 +25,7 @@ import com.djrapitops.plan.system.locale.Locale;
import com.djrapitops.plan.system.locale.lang.PluginLang;
import com.djrapitops.plan.system.settings.config.PlanConfig;
import com.djrapitops.plan.utilities.MiscUtils;
import com.djrapitops.plugin.benchmarking.Timings;
import com.djrapitops.plan.utilities.java.ThrowableUtils;
import com.djrapitops.plugin.logging.L;
import com.djrapitops.plugin.logging.console.PluginLogger;
import com.djrapitops.plugin.logging.error.ErrorHandler;
@ -59,10 +59,9 @@ public class SQLiteDB extends SQLDB {
NetworkContainer.Factory networkContainerFactory,
RunnableFactory runnableFactory,
PluginLogger logger,
Timings timings,
ErrorHandler errorHandler
) {
super(() -> serverInfo.get().getServerUUID(), locale, config, networkContainerFactory, runnableFactory, logger, timings, errorHandler);
super(() -> serverInfo.get().getServerUUID(), locale, config, networkContainerFactory, runnableFactory, logger, errorHandler);
dbName = databaseFile.getName();
this.databaseFile = databaseFile;
}
@ -144,22 +143,12 @@ public class SQLiteDB extends SQLDB {
stopConnectionPingTask();
if (connection != null) {
logger.debug("SQLite Connection close prompted by: " + ThrowableUtils.findCallerAfterClass(Thread.currentThread().getStackTrace(), SQLiteDB.class));
logger.debug("SQLite " + dbName + ": Closed Connection");
MiscUtils.close(connection);
}
}
@Override
public void commit(Connection connection) {
try {
connection.commit();
} catch (SQLException e) {
if (!e.getMessage().contains("cannot commit")) {
errorHandler.log(L.ERROR, this.getClass(), e);
}
}
}
@Override
public void returnToPool(Connection connection) {
// Connection pool not in use, no action required.
@ -188,7 +177,6 @@ public class SQLiteDB extends SQLDB {
private final NetworkContainer.Factory networkContainerFactory;
private final RunnableFactory runnableFactory;
private final PluginLogger logger;
private final Timings timings;
private final ErrorHandler errorHandler;
private PlanFiles files;
@ -201,7 +189,6 @@ public class SQLiteDB extends SQLDB {
NetworkContainer.Factory networkContainerFactory,
RunnableFactory runnableFactory,
PluginLogger logger,
Timings timings,
ErrorHandler errorHandler
) {
this.locale = locale;
@ -211,7 +198,6 @@ public class SQLiteDB extends SQLDB {
this.networkContainerFactory = networkContainerFactory;
this.runnableFactory = runnableFactory;
this.logger = logger;
this.timings = timings;
this.errorHandler = errorHandler;
}
@ -227,7 +213,7 @@ public class SQLiteDB extends SQLDB {
return new SQLiteDB(databaseFile,
locale, config, serverInfo,
networkContainerFactory,
runnableFactory, logger, timings, errorHandler
runnableFactory, logger, errorHandler
);
}

View File

@ -41,7 +41,7 @@ public class BackupCopyTransaction extends RemoveEverythingTransaction {
@Override
protected boolean shouldBeExecuted() {
return !sourceDB.equals(db) && sourceDB.isOpen();
return !sourceDB.equals(db) && sourceDB.getState() != Database.State.CLOSED;
}
@Override

View File

@ -28,6 +28,7 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Savepoint;
import java.util.UUID;
/**
* Represents a database transaction.
@ -37,11 +38,12 @@ import java.sql.Savepoint;
public abstract class Transaction {
protected SQLDB db; // TODO Make private, this is a quick hack to access some tables while they are in use.
protected DBType dbType;
private Connection connection;
private Savepoint savepoint;
private boolean success;
protected boolean success;
protected Transaction() {
success = false;
@ -52,8 +54,10 @@ public abstract class Transaction {
Verify.isFalse(success, () -> new IllegalStateException("Transaction has already been executed"));
this.db = db;
this.dbType = db.getType();
if (!shouldBeExecuted()) {
success = true;
return;
}
@ -103,7 +107,6 @@ public abstract class Transaction {
throw new DBOpException(getClass().getSimpleName() + " finalization failed: " + e.getMessage(), e);
}
if (db != null) db.returnToPool(connection);
db = null;
}
private void handleSavepoint() throws SQLException {
@ -119,7 +122,7 @@ public abstract class Transaction {
}
protected <T> T query(Query<T> query) {
return db.query(query);
return query.executeQuery(db);
}
protected boolean execute(Executable executable) {
@ -152,12 +155,12 @@ public abstract class Transaction {
transaction.connection = null;
}
protected DBType getDBType() {
return db.getType();
}
@Deprecated
protected void setDb(SQLDB db) {
this.db = db;
}
protected UUID getServerUUID() {
return db.getServerUUIDSupplier().get();
}
}

View File

@ -16,7 +16,6 @@
*/
package com.djrapitops.plan.db.access.transactions.init;
import com.djrapitops.plan.db.DBType;
import com.djrapitops.plan.db.sql.tables.*;
/**
@ -29,7 +28,6 @@ public class CreateTablesTransaction extends OperationCriticalTransaction {
@Override
protected void performOperations() {
// DBType is required for SQL parsing, as MySQL and SQLite primary key format differs.
DBType dbType = getDBType();
// Create statements are run in a specific order as some tables have foreign keys,
// or had at some point in the past.

View File

@ -16,6 +16,8 @@
*/
package com.djrapitops.plan.db.access.transactions.init;
import com.djrapitops.plan.api.exceptions.database.FatalDBException;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.access.transactions.Transaction;
/**
@ -27,4 +29,11 @@ import com.djrapitops.plan.db.access.transactions.Transaction;
*/
public abstract class OperationCriticalTransaction extends Transaction {
@Override
public void executeTransaction(SQLDB db) {
super.executeTransaction(db);
if (!success) {
throw new FatalDBException(getClass().getSimpleName() + " failed to execute and database can not be opened.");
}
}
}

View File

@ -16,12 +16,10 @@
*/
package com.djrapitops.plan.db.patches;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.access.HasMoreThanZeroQueryStatement;
import com.djrapitops.plan.db.sql.tables.SessionsTable;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* Patch that resets AFK time of sessions with afk time of length of the session to 0.
@ -35,10 +33,6 @@ import java.sql.SQLException;
*/
public class BadAFKThresholdValuePatch extends Patch {
public BadAFKThresholdValuePatch(SQLDB db) {
super(db);
}
@Override
public boolean hasBeenApplied() {
return !containsSessionsWithFullAFK();
@ -53,7 +47,7 @@ public class BadAFKThresholdValuePatch extends Patch {
")) < 5 AND " + SessionsTable.AFK_TIME + "!=0";
return query(new HasMoreThanZeroQueryStatement(sql, "found") {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
public void prepare(PreparedStatement statement) {
/* Nothing to prepare */
}
});

View File

@ -16,15 +16,10 @@
*/
package com.djrapitops.plan.db.patches;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.sql.tables.TPSTable;
public class DiskUsagePatch extends Patch {
public DiskUsagePatch(SQLDB db) {
super(db);
}
@Override
public boolean hasBeenApplied() {
return hasColumn(TPSTable.TABLE_NAME, TPSTable.FREE_DISK);

View File

@ -16,15 +16,10 @@
*/
package com.djrapitops.plan.db.patches;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.sql.tables.GeoInfoTable;
public class GeoInfoLastUsedPatch extends Patch {
public GeoInfoLastUsedPatch(SQLDB db) {
super(db);
}
@Override
public boolean hasBeenApplied() {
return hasColumn(GeoInfoTable.TABLE_NAME, GeoInfoTable.LAST_USED);

View File

@ -16,7 +16,6 @@
*/
package com.djrapitops.plan.db.patches;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.sql.tables.GeoInfoTable;
public class GeoInfoOptimizationPatch extends Patch {
@ -24,8 +23,7 @@ public class GeoInfoOptimizationPatch extends Patch {
private String tempTableName;
private String tableName;
public GeoInfoOptimizationPatch(SQLDB db) {
super(db);
public GeoInfoOptimizationPatch() {
tableName = GeoInfoTable.TABLE_NAME;
tempTableName = "temp_ips";
}

View File

@ -17,7 +17,6 @@
package com.djrapitops.plan.db.patches;
import com.djrapitops.plan.data.container.GeoInfo;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.access.ExecBatchStatement;
import com.djrapitops.plan.db.access.QueryStatement;
import com.djrapitops.plan.db.access.queries.objects.GeoInfoQueries;
@ -38,8 +37,7 @@ public class IPAnonPatch extends Patch {
private String tableName;
private String tempTableName;
public IPAnonPatch(SQLDB db) {
super(db);
public IPAnonPatch() {
tableName = GeoInfoTable.TABLE_NAME;
tempTableName = "plan_ips_temp";
}
@ -68,7 +66,7 @@ public class IPAnonPatch extends Patch {
@Override
protected void applyPatch() {
Map<UUID, List<GeoInfo>> allGeoInfo = db.query(GeoInfoQueries.fetchAllGeoInformation());
Map<UUID, List<GeoInfo>> allGeoInfo = query(GeoInfoQueries.fetchAllGeoInformation());
anonymizeIPs(allGeoInfo);
groupHashedIPs();
}

View File

@ -16,15 +16,10 @@
*/
package com.djrapitops.plan.db.patches;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.sql.tables.GeoInfoTable;
public class IPHashPatch extends Patch {
public IPHashPatch(SQLDB db) {
super(db);
}
@Override
public boolean hasBeenApplied() {
return hasColumn(GeoInfoTable.TABLE_NAME, GeoInfoTable.IP_HASH);

View File

@ -17,7 +17,6 @@
package com.djrapitops.plan.db.patches;
import com.djrapitops.plan.api.exceptions.database.DBOpException;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.sql.tables.KillsTable;
public class KillsOptimizationPatch extends Patch {
@ -25,8 +24,7 @@ public class KillsOptimizationPatch extends Patch {
private String tempTableName;
private String tableName;
public KillsOptimizationPatch(SQLDB db) {
super(db);
public KillsOptimizationPatch() {
tableName = KillsTable.TABLE_NAME;
tempTableName = "temp_kills";
}

View File

@ -16,7 +16,6 @@
*/
package com.djrapitops.plan.db.patches;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.access.ExecBatchStatement;
import com.djrapitops.plan.db.access.QueryStatement;
import com.djrapitops.plan.db.access.queries.schema.SessionIDServerIDRelationQuery;
@ -29,10 +28,6 @@ import java.util.Map;
public class KillsServerIDPatch extends Patch {
public KillsServerIDPatch(SQLDB db) {
super(db);
}
@Override
public boolean hasBeenApplied() {
String tableName = KillsTable.TABLE_NAME;

View File

@ -17,7 +17,6 @@
package com.djrapitops.plan.db.patches;
import com.djrapitops.plan.data.store.objects.Nickname;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.access.ExecBatchStatement;
import com.djrapitops.plan.db.access.QueryAllStatement;
import com.djrapitops.plan.db.sql.parsing.Select;
@ -31,10 +30,6 @@ import java.util.*;
public class NicknameLastSeenPatch extends Patch {
public NicknameLastSeenPatch(SQLDB db) {
super(db);
}
@Override
public boolean hasBeenApplied() {
return hasColumn(NicknamesTable.TABLE_NAME, NicknamesTable.LAST_USED);

View File

@ -17,7 +17,6 @@
package com.djrapitops.plan.db.patches;
import com.djrapitops.plan.api.exceptions.database.DBOpException;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.sql.tables.NicknamesTable;
public class NicknamesOptimizationPatch extends Patch {
@ -25,8 +24,7 @@ public class NicknamesOptimizationPatch extends Patch {
private String tempTableName;
private String tableName;
public NicknamesOptimizationPatch(SQLDB db) {
super(db);
public NicknamesOptimizationPatch() {
tableName = NicknamesTable.TABLE_NAME;
tempTableName = "temp_nicknames";
}

View File

@ -18,29 +18,18 @@ package com.djrapitops.plan.db.patches;
import com.djrapitops.plan.api.exceptions.database.DBOpException;
import com.djrapitops.plan.db.DBType;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.access.queries.schema.H2SchemaQueries;
import com.djrapitops.plan.db.access.queries.schema.MySQLSchemaQueries;
import com.djrapitops.plan.db.access.queries.schema.SQLiteSchemaQueries;
import com.djrapitops.plan.db.access.transactions.init.OperationCriticalTransaction;
import com.djrapitops.plan.db.sql.parsing.TableSqlParser;
import com.djrapitops.plugin.utilities.Verify;
import java.util.List;
import java.util.UUID;
public abstract class Patch extends OperationCriticalTransaction {
protected final SQLDB db;
protected final DBType dbType;
private static final String ALTER_TABLE = "ALTER TABLE ";
public Patch(SQLDB db) {
setDb(db);
this.db = db;
this.dbType = db.getType();
}
public abstract boolean hasBeenApplied();
protected abstract void applyPatch();
@ -57,11 +46,6 @@ public abstract class Patch extends OperationCriticalTransaction {
if (dbType == DBType.MYSQL) enableForeignKeyChecks();
}
@Deprecated
public void apply() {
db.executeTransaction(this);
}
private void enableForeignKeyChecks() {
execute("SET FOREIGN_KEY_CHECKS=1");
}
@ -101,7 +85,7 @@ public abstract class Patch extends OperationCriticalTransaction {
}
protected void dropTable(String name) {
execute(TableSqlParser.dropTable(name));
execute("DROP TABLE " + name);
}
protected void renameTable(String from, String to) {
@ -143,8 +127,4 @@ public abstract class Patch extends OperationCriticalTransaction {
Verify.isTrue(constraints.isEmpty(), () -> new DBOpException("Table '" + table + "' has constraints '" + constraints + "'"));
}
protected UUID getServerUUID() {
return db.getServerUUIDSupplier().get();
}
}

View File

@ -17,7 +17,6 @@
package com.djrapitops.plan.db.patches;
import com.djrapitops.plan.api.exceptions.database.DBOpException;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.sql.tables.PingTable;
public class PingOptimizationPatch extends Patch {
@ -25,8 +24,7 @@ public class PingOptimizationPatch extends Patch {
private String tempTableName;
private String tableName;
public PingOptimizationPatch(SQLDB db) {
super(db);
public PingOptimizationPatch() {
tableName = PingTable.TABLE_NAME;
tempTableName = "temp_ping";
}

View File

@ -16,15 +16,10 @@
*/
package com.djrapitops.plan.db.patches;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.sql.tables.SessionsTable;
public class SessionAFKTimePatch extends Patch {
public SessionAFKTimePatch(SQLDB db) {
super(db);
}
@Override
public boolean hasBeenApplied() {
return hasColumn(SessionsTable.TABLE_NAME, SessionsTable.AFK_TIME);

View File

@ -17,7 +17,6 @@
package com.djrapitops.plan.db.patches;
import com.djrapitops.plan.api.exceptions.database.DBOpException;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.sql.tables.SessionsTable;
public class SessionsOptimizationPatch extends Patch {
@ -25,8 +24,7 @@ public class SessionsOptimizationPatch extends Patch {
private String tempTableName;
private String tableName;
public SessionsOptimizationPatch(SQLDB db) {
super(db);
public SessionsOptimizationPatch() {
tableName = SessionsTable.TABLE_NAME;
tempTableName = "temp_sessions";
}

View File

@ -16,14 +16,8 @@
*/
package com.djrapitops.plan.db.patches;
import com.djrapitops.plan.db.SQLDB;
public class TransferTableRemovalPatch extends Patch {
public TransferTableRemovalPatch(SQLDB db) {
super(db);
}
@Override
public boolean hasBeenApplied() {
return !hasTable("plan_transfer");

View File

@ -17,7 +17,6 @@
package com.djrapitops.plan.db.patches;
import com.djrapitops.plan.api.exceptions.database.DBOpException;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.sql.tables.UserInfoTable;
public class UserInfoOptimizationPatch extends Patch {
@ -25,8 +24,7 @@ public class UserInfoOptimizationPatch extends Patch {
private String tempTableName;
private String tableName;
public UserInfoOptimizationPatch(SQLDB db) {
super(db);
public UserInfoOptimizationPatch() {
tableName = UserInfoTable.TABLE_NAME;
tempTableName = "temp_user_info";
}

View File

@ -16,7 +16,6 @@
*/
package com.djrapitops.plan.db.patches;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.access.queries.objects.ServerQueries;
import com.djrapitops.plan.db.sql.tables.*;
import com.djrapitops.plan.system.info.server.Server;
@ -29,10 +28,6 @@ public class Version10Patch extends Patch {
private Integer serverID;
public Version10Patch(SQLDB db) {
super(db);
}
@Override
public boolean hasBeenApplied() {
return !hasTable("plan_gamemodetimes");
@ -40,7 +35,7 @@ public class Version10Patch extends Patch {
@Override
protected void applyPatch() {
Optional<Server> server = db.query(ServerQueries.fetchServerMatchingIdentifier(getServerUUID()));
Optional<Server> server = query(ServerQueries.fetchServerMatchingIdentifier(getServerUUID()));
serverID = server.map(Server::getId)
.orElseThrow(() -> new IllegalStateException("Server UUID was not registered, try rebooting the plugin."));

View File

@ -16,14 +16,8 @@
*/
package com.djrapitops.plan.db.patches;
import com.djrapitops.plan.db.SQLDB;
public class VersionTableRemovalPatch extends Patch {
public VersionTableRemovalPatch(SQLDB db) {
super(db);
}
@Override
public boolean hasBeenApplied() {
return !hasTable("plan_version");

View File

@ -17,7 +17,6 @@
package com.djrapitops.plan.db.patches;
import com.djrapitops.plan.api.exceptions.database.DBOpException;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.sql.tables.WorldTimesTable;
public class WorldTimesOptimizationPatch extends Patch {
@ -25,8 +24,7 @@ public class WorldTimesOptimizationPatch extends Patch {
private String tempTableName;
private String tableName;
public WorldTimesOptimizationPatch(SQLDB db) {
super(db);
public WorldTimesOptimizationPatch() {
tableName = WorldTimesTable.TABLE_NAME;
tempTableName = "temp_world_times";
}

View File

@ -16,7 +16,6 @@
*/
package com.djrapitops.plan.db.patches;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.access.ExecBatchStatement;
import com.djrapitops.plan.db.access.QueryStatement;
import com.djrapitops.plan.db.access.queries.schema.SessionIDServerIDRelationQuery;
@ -29,10 +28,6 @@ import java.util.Map;
public class WorldTimesSeverIDPatch extends Patch {
public WorldTimesSeverIDPatch(SQLDB db) {
super(db);
}
@Override
public boolean hasBeenApplied() {
String tableName = WorldTimesTable.TABLE_NAME;

View File

@ -17,7 +17,6 @@
package com.djrapitops.plan.db.patches;
import com.djrapitops.plan.api.exceptions.database.DBOpException;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.sql.tables.WorldTable;
public class WorldsOptimizationPatch extends Patch {
@ -25,8 +24,7 @@ public class WorldsOptimizationPatch extends Patch {
private String tempTableName;
private String tableName;
public WorldsOptimizationPatch(SQLDB db) {
super(db);
public WorldsOptimizationPatch() {
tableName = WorldTable.TABLE_NAME;
tempTableName = "temp_worlds";
}

View File

@ -16,7 +16,6 @@
*/
package com.djrapitops.plan.db.patches;
import com.djrapitops.plan.db.SQLDB;
import com.djrapitops.plan.db.access.ExecBatchStatement;
import com.djrapitops.plan.db.access.QueryAllStatement;
import com.djrapitops.plan.db.access.QueryStatement;
@ -37,10 +36,6 @@ import static com.djrapitops.plan.db.sql.parsing.Sql.*;
public class WorldsServerIDPatch extends Patch {
public WorldsServerIDPatch(SQLDB db) {
super(db);
}
@Override
public boolean hasBeenApplied() {
String tableName = WorldTable.TABLE_NAME;
@ -69,7 +64,7 @@ public class WorldsServerIDPatch extends Patch {
@Override
protected void applyPatch() {
Collection<UUID> serverUUIDs = db.query(ServerQueries.fetchPlanServerInformation()).keySet();
Collection<UUID> serverUUIDs = query(ServerQueries.fetchPlanServerInformation()).keySet();
Map<UUID, Collection<String>> worldsPerServer = new HashMap<>();
for (UUID serverUUID : serverUUIDs) {

View File

@ -80,14 +80,19 @@ public class ServerServerInfo extends ServerInfo {
try {
return serverUUID.isPresent() ? updateDbInfo(serverUUID.get()) : registerServer();
} catch (DBOpException e) {
String causeMsg = e.getCause().getMessage();
String causeMsg = e.getMessage();
throw new EnableException("Failed to read Server information from Database: " + causeMsg, e);
} catch (IOException e) {
throw new EnableException("Failed to read ServerInfoFile.yml", e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null; // This line is not reached due to the thread interrupt.
} catch (Exception e) {
throw new EnableException("Failed to perform a database transaction to store the server information", e);
}
}
private Server updateDbInfo(UUID serverUUID) throws IOException {
private Server updateDbInfo(UUID serverUUID) throws Exception {
Database db = dbSystem.getDatabase();
Optional<Server> foundServer = db.query(ServerQueries.fetchServerMatchingIdentifier(serverUUID));
@ -111,11 +116,11 @@ public class ServerServerInfo extends ServerInfo {
return server;
}
private Server registerServer() throws IOException {
private Server registerServer() throws Exception {
return registerServer(generateNewUUID());
}
private Server registerServer(UUID serverUUID) throws IOException {
private Server registerServer(UUID serverUUID) throws Exception {
Database db = dbSystem.getDatabase();
// Create the server object
@ -125,7 +130,8 @@ public class ServerServerInfo extends ServerInfo {
Server server = new Server(-1, serverUUID, name, webAddress, maxPlayers);
// Save
db.executeTransaction(new StoreServerInformationTransaction(server));
db.executeTransaction(new StoreServerInformationTransaction(server))
.get(); // Wait until transaction has completed
// Load from database
server = db.query(ServerQueries.fetchServerMatchingIdentifier(serverUUID))

View File

@ -0,0 +1,53 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.utilities.java;
import com.djrapitops.plugin.utilities.ArrayUtil;
/**
* Utilities for manipulating different Throwables.
*
* @author Rsl1122
*/
public class ThrowableUtils {
private ThrowableUtils() {
/* Static method class */
}
public static void appendEntryPointToCause(Throwable throwable, Throwable originPoint) {
Throwable cause = throwable.getCause();
while (cause.getCause() != null) {
cause = cause.getCause();
}
cause.setStackTrace(ArrayUtil.merge(cause.getStackTrace(), originPoint.getStackTrace()));
}
public static String findCallerAfterClass(StackTraceElement[] stackTrace, Class afterThis) {
boolean found = false;
for (StackTraceElement stackTraceElement : stackTrace) {
if (found) {
return stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName();
}
if (stackTraceElement.getClassName().contains(afterThis.getName())) {
found = true;
}
}
return "Unknown";
}
}

View File

@ -55,6 +55,7 @@ import com.djrapitops.plan.system.settings.paths.WebserverSettings;
import com.djrapitops.plan.utilities.SHA256Hash;
import com.djrapitops.plan.utilities.comparators.DateHolderRecentComparator;
import com.djrapitops.plugin.logging.console.TestPluginLogger;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.*;
import org.junit.rules.TemporaryFolder;
import org.junit.rules.Timeout;
@ -108,7 +109,7 @@ public abstract class CommonDBTest {
dbSystem = system.getDatabaseSystem();
db = (SQLDB) dbSystem.getActiveDatabaseByName(dbName);
db.setTransactionExecutorServiceProvider(MoreExecutors::newDirectExecutorService);
db.init();
serverUUID = system.getServerInfo().getServerUUID();
@ -120,8 +121,8 @@ public abstract class CommonDBTest {
}
@Before
public void setUp() throws DBInitException {
new Patch(db) {
public void setUp() {
db.executeTransaction(new Patch() {
@Override
public boolean hasBeenApplied() {
return false;
@ -135,7 +136,7 @@ public abstract class CommonDBTest {
dropTable("plan_worlds");
dropTable("plan_users");
}
}.apply();
});
db.executeTransaction(new CreateTablesTransaction());
db.executeTransaction(new RemoveEverythingTransaction());
@ -157,13 +158,6 @@ public abstract class CommonDBTest {
db.init();
}
@Test
public void testNoExceptionWhenCommitEmpty() throws Exception {
db.commit(db.getConnection());
db.commit(db.getConnection());
db.commit(db.getConnection());
}
@Test
public void testSaveCommandUse() throws DBInitException {
Map<String, Integer> expected = new HashMap<>();
@ -243,19 +237,20 @@ public abstract class CommonDBTest {
}
@Test
public void geoInformationIsStored() throws DBInitException {
public void geoInformationIsStored() throws DBInitException, NoSuchAlgorithmException {
saveUserOne();
String expectedIP = "1.2.3.4";
String expectedGeoLoc = "TestLocation";
long time = System.currentTimeMillis();
GeoInfo expected = new GeoInfo(expectedIP, expectedGeoLoc, time, "3");
saveGeoInfo(playerUUID, expected);
saveGeoInfo(playerUUID, new GeoInfo(expectedIP, expectedGeoLoc, time, "3"));
commitTest();
List<GeoInfo> geolocations = db.query(GeoInfoQueries.fetchAllGeoInformation()).getOrDefault(playerUUID, new ArrayList<>());
assertEquals(1, geolocations.size());
GeoInfo expected = new GeoInfo("1.2.xx.xx", expectedGeoLoc, time, new SHA256Hash(expectedIP).create());
assertEquals(expected, geolocations.get(0));
}
@ -651,6 +646,7 @@ public abstract class CommonDBTest {
@Test
public void testBackupAndRestore() throws Exception {
H2DB backup = dbSystem.getH2Factory().usingFile(temporaryFolder.newFile("backup.db"));
backup.setTransactionExecutorServiceProvider(MoreExecutors::newDirectExecutorService);
backup.init();
saveAllData();

View File

@ -26,6 +26,7 @@ import com.djrapitops.plan.db.access.transactions.init.CreateTablesTransaction;
import com.djrapitops.plan.db.patches.Patch;
import com.djrapitops.plan.system.settings.config.PlanConfig;
import com.djrapitops.plan.system.settings.paths.DatabaseSettings;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@ -35,8 +36,6 @@ import rules.PluginComponentMocker;
import utilities.OptionalAssert;
import utilities.TestConstants;
import java.sql.SQLException;
/**
* Test for the patching of Plan 4.5.2 H2 DB into the newest schema.
*
@ -67,7 +66,7 @@ public class DBPatchH2RegressionTest extends DBPatchRegressionTest {
private H2DB underTest;
@Before
public void setUpDBWithOldSchema() throws DBInitException, SQLException {
public void setUpDBWithOldSchema() throws DBInitException {
PlanConfig config = component.getPlanSystem().getConfigSystem().getConfig();
config.set(DatabaseSettings.MYSQL_USER, "user");
@ -75,11 +74,11 @@ public class DBPatchH2RegressionTest extends DBPatchRegressionTest {
underTest = component.getPlanSystem().getDatabaseSystem().getH2Factory()
.usingFileCalled("test");
underTest.setOpen(true);
underTest.setupDataSource();
underTest.setTransactionExecutorServiceProvider(MoreExecutors::newDirectExecutorService);
underTest.init();
// Initialize database with the old table schema
dropAllTables(underTest);
underTest.executeTransaction(new Transaction() {
@Override
protected void performOperations() {
@ -112,11 +111,12 @@ public class DBPatchH2RegressionTest extends DBPatchRegressionTest {
@Test
public void h2PatchTaskWorksWithoutErrors() {
for (Patch patch : underTest.patches()) {
Patch[] patches = underTest.patches();
for (Patch patch : patches) {
underTest.executeTransaction(patch);
}
assertPatchesHaveBeenApplied(underTest);
assertPatchesHaveBeenApplied(patches);
// Make sure that a fetch works.
ServerContainer server = underTest.query(ContainerFetchQueries.fetchServerContainer(TestConstants.SERVER_UUID));

View File

@ -29,6 +29,7 @@ import com.djrapitops.plan.system.PlanSystem;
import com.djrapitops.plan.system.settings.config.PlanConfig;
import com.djrapitops.plan.system.settings.paths.DatabaseSettings;
import com.djrapitops.plan.system.settings.paths.WebserverSettings;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.*;
import org.junit.rules.TemporaryFolder;
import rules.PluginComponentMocker;
@ -90,11 +91,10 @@ public class DBPatchMySQLRegressionTest extends DBPatchRegressionTest {
system.enable();
underTest = (MySQLDB) system.getDatabaseSystem().getActiveDatabaseByName(DBType.MYSQL.getName());
underTest.setTransactionExecutorServiceProvider(MoreExecutors::newDirectExecutorService);
underTest.init();
underTest.setOpen(true);
underTest.setupDataSource();
dropAllTables();
dropAllTables(underTest);
// Initialize database with the old table schema
underTest.executeTransaction(new Transaction() {
@ -122,17 +122,6 @@ public class DBPatchMySQLRegressionTest extends DBPatchRegressionTest {
insertData(underTest);
}
private void dropAllTables() {
underTest.executeTransaction(new Transaction() {
@Override
protected void performOperations() {
execute("DROP DATABASE Plan");
execute("CREATE DATABASE Plan");
execute("USE Plan");
}
});
}
@After
public void closeDatabase() {
underTest.close();
@ -140,11 +129,12 @@ public class DBPatchMySQLRegressionTest extends DBPatchRegressionTest {
@Test
public void mysqlPatchTaskWorksWithoutErrors() {
for (Patch patch : underTest.patches()) {
Patch[] patches = underTest.patches();
for (Patch patch : patches) {
underTest.executeTransaction(patch);
}
assertPatchesHaveBeenApplied(underTest);
assertPatchesHaveBeenApplied(patches);
// Make sure that a fetch works.
ServerContainer server = underTest.query(ContainerFetchQueries.fetchServerContainer(TestConstants.SERVER_UUID));

View File

@ -16,7 +16,9 @@
*/
package com.djrapitops.plan.db;
import com.djrapitops.plan.db.access.transactions.Transaction;
import com.djrapitops.plan.db.patches.Patch;
import com.djrapitops.plan.db.sql.tables.*;
import utilities.TestConstants;
import java.util.ArrayList;
@ -37,23 +39,49 @@ public abstract class DBPatchRegressionTest {
String insertWorld = "INSERT INTO plan_worlds (server_id, world_name) VALUES (1, 'World')";
String insertWorldTimes = "INSERT INTO plan_world_times (user_id, server_id, world_id, session_id, survival_time) VALUES (1,1,1,1,1234)";
protected void insertData(SQLDB underTest) {
underTest.execute(insertServer);
underTest.execute(insertUser);
underTest.execute(insertUser2);
underTest.execute(insertUserInfo);
underTest.execute(insertIP);
underTest.execute(insertNickname);
underTest.execute(insertSession);
underTest.execute(insertKill);
underTest.execute(insertWorld);
underTest.execute(insertWorldTimes);
protected void dropAllTables(SQLDB underTest) {
underTest.executeTransaction(new Transaction() {
@Override
protected void performOperations() {
execute("DROP TABLE " + CommandUseTable.TABLE_NAME);
execute("DROP TABLE " + GeoInfoTable.TABLE_NAME);
execute("DROP TABLE " + KillsTable.TABLE_NAME);
execute("DROP TABLE " + NicknamesTable.TABLE_NAME);
execute("DROP TABLE " + PingTable.TABLE_NAME);
execute("DROP TABLE " + SecurityTable.TABLE_NAME);
execute("DROP TABLE " + ServerTable.TABLE_NAME);
execute("DROP TABLE " + SessionsTable.TABLE_NAME);
execute("DROP TABLE " + SettingsTable.TABLE_NAME);
execute("DROP TABLE " + TPSTable.TABLE_NAME);
execute("DROP TABLE " + UserInfoTable.TABLE_NAME);
execute("DROP TABLE " + UsersTable.TABLE_NAME);
execute("DROP TABLE " + WorldTable.TABLE_NAME);
execute("DROP TABLE " + WorldTimesTable.TABLE_NAME);
}
});
}
protected void assertPatchesHaveBeenApplied(SQLDB underTest) {
protected void insertData(SQLDB underTest) {
underTest.executeTransaction(new Transaction() {
@Override
protected void performOperations() {
execute(insertServer);
execute(insertUser);
execute(insertUser2);
execute(insertUserInfo);
execute(insertIP);
execute(insertNickname);
execute(insertSession);
execute(insertKill);
execute(insertWorld);
execute(insertWorldTimes);
}
});
}
protected void assertPatchesHaveBeenApplied(Patch[] patches) {
List<String> failed = new ArrayList<>();
for (Patch patch : underTest.patches()) {
for (Patch patch : patches) {
if (!patch.hasBeenApplied()) {
failed.add(patch.getClass().getSimpleName());
}

View File

@ -24,6 +24,7 @@ import com.djrapitops.plan.db.access.transactions.Transaction;
import com.djrapitops.plan.db.access.transactions.commands.RemoveEverythingTransaction;
import com.djrapitops.plan.db.access.transactions.init.CreateTablesTransaction;
import com.djrapitops.plan.db.patches.Patch;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@ -33,8 +34,6 @@ import rules.PluginComponentMocker;
import utilities.OptionalAssert;
import utilities.TestConstants;
import java.sql.SQLException;
/**
* Test for the patching of Plan 4.5.2 SQLite DB into the newest schema.
*
@ -65,14 +64,14 @@ public class DBPatchSQLiteRegressionTest extends DBPatchRegressionTest {
private SQLiteDB underTest;
@Before
public void setUpDBWithOldSchema() throws DBInitException, SQLException {
public void setUpDBWithOldSchema() throws DBInitException {
underTest = component.getPlanSystem().getDatabaseSystem().getSqLiteFactory()
.usingFileCalled("test");
underTest.setOpen(true);
underTest.setupDataSource();
underTest.setTransactionExecutorServiceProvider(MoreExecutors::newDirectExecutorService);
underTest.init();
// Initialize database with the old table schema
dropAllTables(underTest);
underTest.executeTransaction(new Transaction() {
@Override
protected void performOperations() {
@ -105,11 +104,12 @@ public class DBPatchSQLiteRegressionTest extends DBPatchRegressionTest {
@Test
public void sqlitePatchTaskWorksWithoutErrors() {
for (Patch patch : underTest.patches()) {
Patch[] patches = underTest.patches();
for (Patch patch : patches) {
underTest.executeTransaction(patch);
}
assertPatchesHaveBeenApplied(underTest);
assertPatchesHaveBeenApplied(patches);
// Make sure that a fetch works.
ServerContainer server = underTest.query(ContainerFetchQueries.fetchServerContainer(TestConstants.SERVER_UUID));

View File

@ -18,6 +18,7 @@ package com.djrapitops.plan.db;
import com.djrapitops.plan.data.container.GeoInfo;
import com.djrapitops.plan.db.access.queries.ServerAggregateQueries;
import com.djrapitops.plan.db.access.transactions.Transaction;
import com.djrapitops.plan.db.access.transactions.events.PlayerRegisterTransaction;
import com.djrapitops.plan.system.settings.config.PlanConfig;
import com.djrapitops.plan.system.settings.paths.DatabaseSettings;
@ -59,9 +60,14 @@ public class MySQLTest extends CommonDBTest {
}
private static void clearDatabase() {
db.execute("DROP DATABASE Plan");
db.execute("CREATE DATABASE Plan");
db.execute("USE Plan");
db.executeTransaction(new Transaction() {
@Override
protected void performOperations() {
execute("DROP DATABASE Plan");
execute("CREATE DATABASE Plan");
execute("USE Plan");
}
});
}
@Test

View File

@ -31,6 +31,7 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
/**
* Manages Server information on the Bungee instance.
@ -70,8 +71,10 @@ public class VelocityServerInfo extends ServerInfo {
} else {
server = registerVelocityInfo(database);
}
} catch (DBOpException e) {
} catch (DBOpException | ExecutionException e) {
throw new EnableException("Failed to read Server information from Database.");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return server;
}
@ -93,13 +96,14 @@ public class VelocityServerInfo extends ServerInfo {
}
}
private Server registerVelocityInfo(Database db) throws EnableException {
private Server registerVelocityInfo(Database db) throws EnableException, ExecutionException, InterruptedException {
UUID serverUUID = generateNewUUID();
String accessAddress = webServer.get().getAccessAddress();
// TODO Rework to allow Velocity as name.
Server proxy = new Server(-1, serverUUID, "BungeeCord", accessAddress, serverProperties.getMaxPlayers());
db.executeTransaction(new StoreServerInformationTransaction(proxy));
db.executeTransaction(new StoreServerInformationTransaction(proxy))
.get();
Optional<Server> proxyInfo = db.query(ServerQueries.fetchProxyServerInformation());
if (proxyInfo.isPresent()) {

View File

@ -16,11 +16,13 @@
*/
package com.djrapitops.plan;
import com.djrapitops.plan.db.SQLiteDB;
import com.djrapitops.plan.system.PlanSystem;
import com.djrapitops.plan.system.database.DBSystem;
import com.djrapitops.plan.system.settings.config.PlanConfig;
import com.djrapitops.plan.system.settings.paths.ProxySettings;
import com.djrapitops.plan.system.settings.paths.WebserverSettings;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@ -56,7 +58,9 @@ public class VelocitySystemTest {
config.set(ProxySettings.IP, "8.8.8.8");
DBSystem dbSystem = velocitySystem.getDatabaseSystem();
dbSystem.setActiveDatabase(dbSystem.getSqLiteFactory().usingDefaultFile());
SQLiteDB db = dbSystem.getSqLiteFactory().usingDefaultFile();
db.setTransactionExecutorServiceProvider(MoreExecutors::newDirectExecutorService);
dbSystem.setActiveDatabase(db);
velocitySystem.enable();
assertTrue(velocitySystem.isEnabled());