Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 39 additions & 42 deletions src/main/java/me/axeno/hommr/managers/DatabaseManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,76 +2,73 @@

import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.jdbc.JdbcConnectionSource;
import com.j256.ormlite.jdbc.JdbcPooledConnectionSource;
import com.j256.ormlite.misc.TransactionManager;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;
import lombok.Getter;
import me.axeno.hommr.Hommr;
import me.axeno.hommr.models.Home;
import org.bukkit.configuration.file.FileConfiguration;

import java.io.File;
import java.sql.SQLException;
import java.util.List;
import java.util.concurrent.Callable;

public class DatabaseManager {

private ConnectionSource connectionSource;
@Getter
private Dao<Home, Integer> homeDao;

/**
* Set up the database connection and DAO for Home entities based on configuration.
*
* Initializes the plugin data folder if necessary, creates a JDBC connection source
* (MySQL when `database.type` is `"mysql"`, otherwise SQLite using a `homes.db` file),
* and creates a Dao<Home, Integer> for accessing Home records. Ensures the Home table
* exists in the database; failures during folder creation, table creation, or overall
* initialization are logged.
*/
public void init() {
try {
File dataFolder = Hommr.getInstance().getDataFolder();
if (!dataFolder.exists()) {
boolean created = dataFolder.mkdirs();
if (!created) {
Hommr.getInstance().getLogger().warning("Could not create plugin data folder: " + dataFolder.getAbsolutePath());
}
}

String type = Hommr.getInstance().getConfig().getString("database.type", "sqlite").toLowerCase();
String databaseUrl;
String username = null;
String password = null;
public void init(String dbUrl, String dbUser, String dbPassword) {
if (dbUrl == null || dbUrl.isEmpty()) {
throw new IllegalStateException("Database URL is not configured. Please set 'database.connection.url' in config.yml");
}

if (type.equals("mysql")) {
databaseUrl = Hommr.getInstance().getConfig().getString("database.connection.url");
username = Hommr.getInstance().getConfig().getString("database.connection.username");
password = Hommr.getInstance().getConfig().getString("database.connection.password");
if (databaseUrl == null || databaseUrl.isEmpty()) {
throw new IllegalArgumentException("MySQL database URL is required but not configured. Please check your config.yml.");
}
connectionSource = new JdbcConnectionSource(databaseUrl, username, password);
} else {
databaseUrl = "jdbc:sqlite:" + new File(dataFolder, "homes.db").getAbsolutePath();
connectionSource = new JdbcConnectionSource(databaseUrl);
if (dbUrl.startsWith("jbdc:mysql:")) {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
Hommr.getInstance().getSLF4JLogger().error("MySQL database driver not found. Please ensure the MySQL driver is included in the classpath.");
throw new RuntimeException(e);
}
}

homeDao = DaoManager.createDao(connectionSource, Home.class);
try {
connectionSource = new JdbcPooledConnectionSource(dbUrl, dbUser, dbPassword);

TableUtils.createTableIfNotExists(connectionSource, Home.class);
homeDao = DaoManager.createDao(connectionSource, Home.class);
if (!homeDao.isTableExists()) {
TableUtils.createTableIfNotExists(connectionSource, Home.class);
}

} catch (SQLException e) {
Hommr.getInstance().getLogger().log(java.util.logging.Level.SEVERE, "Failed to initialize database", e);
throw new RuntimeException("Database initialization failed", e);
}
}

/**
* Set up the database connection and DAO for Home entities based on configuration.
* <p>
* Initializes the plugin data folder if necessary, creates a JDBC connection source
* and creates a Dao<Home, Integer> for accessing Home records. Ensures the Home table
* exists in the database; failures during folder creation, table creation, or overall
* initialization are logged.
*/
public void init() {
FileConfiguration config = Hommr.getInstance().getConfig();
String dbUrl = config.getString("database.connection.url");
String dbUser = config.getString("database.connection.username", "");
String dbPassword = config.getString("database.connection.password", "");

init(dbUrl, dbUser, dbPassword);
}

/**
* Closes the underlying database connection source and releases related resources.
*
* If an error occurs while closing, the exception is caught and a warning is logged.
* <p>
* If an error occurs while closing, the exception is caught and a warning is logged.
*/
public void close() {
if (connectionSource != null) {
Expand All @@ -96,7 +93,7 @@ public List<Home> getAllHomes() throws SQLException {

/**
* Replaces all stored Home records with the provided list.
*
* <p>
* Clears the Home table and inserts the given homes in a single batch operation.
*
* @param homes the list of Home objects to persist (may be empty)
Expand Down
6 changes: 0 additions & 6 deletions src/main/resources/config.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
# Hommr Configuration

database:
# Database type to use for storage.
# Available options:
# - sqlite: Stores data in a local file (homes.db) within the plugin folder. No extra setup required.
# - mysql: Stores data in an external MySQL database. Requires connection details below.
type: sqlite

connection:
# JDBC URL for the database connection (Required for MySQL).
# Format for MySQL: jdbc:mysql://<host>:<port>/<database>
Expand Down
25 changes: 5 additions & 20 deletions src/test/java/me/axeno/hommr/managers/DatabaseManagerTest.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package me.axeno.hommr.managers;

import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.support.ConnectionSource;
import me.axeno.hommr.Hommr;
import me.axeno.hommr.models.Home;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.PluginDescriptionFile;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -15,7 +12,6 @@
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;

import java.io.File;
import java.nio.file.Path;
import java.sql.SQLException;
import java.util.ArrayList;
Expand Down Expand Up @@ -45,16 +41,17 @@ class DatabaseManagerTest {
private MockedStatic<Hommr> hommrMockedStatic;

@BeforeEach
void setUp() {
void setUp() throws SQLException {
// Use SQLite in-memory database for testing
databaseManager = new DatabaseManager();
databaseManager.init("jdbc:sqlite::memory:", "", "");
Comment on lines +44 to +47
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find the DatabaseManager class
fd "DatabaseManager\.java" --type f

Repository: AxenoDev/Hommr

Length of output: 116


🏁 Script executed:

# Check project Java version
fd "pom\.xml|build\.gradle" --type f

Repository: AxenoDev/Hommr

Length of output: 70


🏁 Script executed:

# Look at the test file to understand the changes
cat -n src/test/java/me/axeno/hommr/managers/DatabaseManagerTest.java

Repository: AxenoDev/Hommr

Length of output: 5782


🏁 Script executed:

# Check DatabaseManager init method
sed -n '1,100p' src/main/java/me/axeno/hommr/managers/DatabaseManager.java

Repository: AxenoDev/Hommr

Length of output: 3918


🏁 Script executed:

# Check Java version in build.gradle
cat build.gradle

Repository: AxenoDev/Hommr

Length of output: 5748


Replace in-memory SQLite with a file-based database to avoid flaky tests with connection pooling.

JdbcPooledConnectionSource creates multiple pooled connections, and SQLite's ::memory: URI creates a new isolated in-memory database per connection. Saves and queries can hit different databases, causing test failures. Use the available @TempDir to create a file-based test database instead.

Suggested fix
     void setUp() throws SQLException {
         // Use SQLite in-memory database for testing
         databaseManager = new DatabaseManager();
-        databaseManager.init("jdbc:sqlite::memory:", "", "");
+        Path dbFile = tempDir.resolve("test.db");
+        databaseManager.init("jdbc:sqlite:" + dbFile.toString(), "", "");
🤖 Prompt for AI Agents
In `@src/test/java/me/axeno/hommr/managers/DatabaseManagerTest.java` around lines
44 - 47, The test uses an in-memory SQLite URI which creates isolated DBs per
pooled connection; update the setUp method to use a file-based DB created from
the JUnit `@TempDir` instead: create a temporary File inside the `@TempDir` and pass
its path into DatabaseManager.init (use "jdbc:sqlite:" +
tempFile.getAbsolutePath()), leaving DatabaseManager and setUp as the locations
to change so pooled connections share the same file-backed database.


hommrMockedStatic = mockStatic(Hommr.class);
hommrMockedStatic.when(Hommr::getInstance).thenReturn(mockPlugin);

lenient().when(mockPlugin.getDataFolder()).thenReturn(tempDir.toFile());
lenient().when(mockPlugin.getConfig()).thenReturn(mockConfig);
lenient().when(mockPlugin.getLogger()).thenReturn(mockLogger);
lenient().when(mockConfig.getString("database.type", "sqlite")).thenReturn("sqlite");
}

@AfterEach
Expand All @@ -69,17 +66,11 @@ void tearDown() {

@Test
void testInitWithSQLiteCreatesDatabase() {
databaseManager.init();

assertNotNull(databaseManager.getHomeDao());
File dbFile = new File(tempDir.toFile(), "homes.db");
assertTrue(dbFile.exists());
}

@Test
void testSaveAndGetAllHomes() throws SQLException {
databaseManager.init();

List<Home> homesToSave = new ArrayList<>();
homesToSave.add(createTestHome(UUID.randomUUID(), "home1"));
homesToSave.add(createTestHome(UUID.randomUUID(), "home2"));
Expand All @@ -94,8 +85,6 @@ void testSaveAndGetAllHomes() throws SQLException {

@Test
void testSaveAllHomesClearsExistingHomes() throws SQLException {
databaseManager.init();

List<Home> firstBatch = new ArrayList<>();
firstBatch.add(createTestHome(UUID.randomUUID(), "home1"));
firstBatch.add(createTestHome(UUID.randomUUID(), "home2"));
Expand All @@ -108,13 +97,11 @@ void testSaveAllHomesClearsExistingHomes() throws SQLException {
List<Home> retrievedHomes = databaseManager.getAllHomes();

assertEquals(1, retrievedHomes.size());
assertEquals("home3", retrievedHomes.get(0).getName());
assertEquals("home3", retrievedHomes.getFirst().getName());
}

@Test
void testSaveAllHomesPreservesHomeData() throws SQLException {
databaseManager.init();

UUID ownerId = UUID.randomUUID();
Home originalHome = new Home(
0,
Expand All @@ -133,7 +120,7 @@ void testSaveAllHomesPreservesHomeData() throws SQLException {
List<Home> retrievedHomes = databaseManager.getAllHomes();

assertEquals(1, retrievedHomes.size());
Home retrievedHome = retrievedHomes.get(0);
Home retrievedHome = retrievedHomes.getFirst();
assertEquals(ownerId, retrievedHome.getOwner());
assertEquals("testHome", retrievedHome.getName());
assertEquals("world", retrievedHome.getWorld());
Expand All @@ -146,8 +133,6 @@ void testSaveAllHomesPreservesHomeData() throws SQLException {

@Test
void testCloseDoesNotThrowException() {
databaseManager.init();

assertDoesNotThrow(() -> databaseManager.close());
}

Expand Down