-
Notifications
You must be signed in to change notification settings - Fork 0
Storage
PrisonCore picks one storage backend at boot based on core.yml and exposes that backend to every module through a single registry. Modules don't pick their own backend — the platform-wide selection is the contract, and your module either supports the active backend or it doesn't. In practice this is fine because all four supported backends (JSON, SQLite, SQL/MySQL, MongoDB) cover the common cases.
The kernel manages the lifecycle (connect, disconnect, health checks). Your module asks for the active backend, casts it to the concrete class for the type it expects, and uses the underlying handle directly.
com.github.frosxt.prisoncore.kernel.storage.StorageRegistry
Resolved through the service container:
final StorageRegistry storage = context.services().resolve(StorageRegistry.class);The relevant methods:
public StorageBackend getOrCreate(String type, String scope, Map<String, Object> config);
public StorageBackend getOrCreate(String type, Map<String, Object> config);
public Optional<StorageBackend> getActive(String type);
public Optional<StorageBackend> getActive(String type, String scope);
public boolean hasFactory(String type);
public Set<String> availableTypes();
public void registerFactory(StorageBackendFactory factory);getOrCreate(type, scope, config) is the workhorse. It looks up the active backend for type:scope, creates and connects it on first call, and returns it on subsequent calls. The scope argument lets two modules share a backend type without colliding — for example, getOrCreate("sql", "economy", ...) and getOrCreate("sql", "skills", ...) get independent connection pools.
getOrCreate(type, config) is shorthand for the unscoped (default) instance.
registerFactory(factory) adds a new backend type. You only call this from a PRE_INFRASTRUCTURE module that contributes a custom backend implementation.
com.github.frosxt.prisoncore.kernel.config.CoreConfig
The active backend is configured platform-wide in core.yml:
storage:
backend: sqliteYou read it via CoreConfig, which is registered in the service container:
final CoreConfig coreConfig = context.services().resolve(CoreConfig.class);
final String backendType = coreConfig.storageBackend();
final Map<String, String> settings = coreConfig.storageProperties();storageProperties() returns every key under storage in core.yml except backend itself, so SQL credentials, the Mongo URI, sqlite file path, and so on all flow through here.
com.github.frosxt.prisoncore.spi.storage.StorageBackend
The minimal contract for any backend:
public interface StorageBackend {
String name();
void connect() throws Exception;
void disconnect();
boolean isHealthy();
}You don't call these methods yourself. The registry calls connect() on first acquisition and disconnect() during kernel shutdown. You get the backend already connected.
To do anything useful with the handle you cast it to the concrete class for the active type.
Each backend is a top-level class with a typed accessor for its underlying handle. The pattern is the same in every case: getOrCreate, instanceof-check, cast, use.
com.github.frosxt.prisoncore.storage.json.JsonStorageBackend
public Path directory();The "handle" is just the root data directory. You write your own files underneath it with whatever schema you want.
final StorageBackend backend = storage.getOrCreate("json", "hello", Map.of("directory",
context.dataFolder().resolve("data").toString()));
if (backend instanceof final JsonStorageBackend json) {
final Path file = json.directory().resolve("greetings.json");
// read or write the file
}com.github.frosxt.prisoncore.storage.sqlite.SqliteBackend
public Connection connection();Owns a single JDBC connection to a file-based database. The connection is shared, so don't close it. Use it directly for prepared statements:
if (backend instanceof final SqliteBackend sqlite) {
try (final PreparedStatement ps = sqlite.connection().prepareStatement(
"INSERT INTO greetings(player_id, text) VALUES (?, ?)")) {
ps.setString(1, playerId.toString());
ps.setString(2, text);
ps.executeUpdate();
}
}The file config key controls the database file path. If you don't want users picking the location, hardcode it relative to your module data folder when you build the config map.
com.github.frosxt.prisoncore.storage.sql.SqlBackend
public Connection connection() throws SQLException;Backed by a HikariCP connection pool. Each call to connection() borrows a connection from the pool. You must close it (try-with-resources) to return it.
if (backend instanceof final SqlBackend sql) {
try (final Connection conn = sql.connection();
final PreparedStatement ps = conn.prepareStatement(...)) {
// ...
}
}The pool is sized via the pool-size setting in core.yml. Default url is jdbc:mysql://localhost:3306/prisoncore.
com.github.frosxt.prisoncore.storage.mongo.MongoBackend
public MongoDatabase database();Returns the connected MongoDatabase from the official Mongo Java driver. From there you use the driver normally:
if (backend instanceof final MongoBackend mongo) {
final MongoCollection<Document> collection = mongo.database().getCollection("greetings");
collection.insertOne(new Document("playerId", playerId.toString()).append("text", text));
}com.github.frosxt.prisoncore.spi.storage.StorageBackendFactory
Only relevant if you are contributing a new backend type from a PRE_INFRASTRUCTURE module.
public interface StorageBackendFactory {
String type();
StorageBackend create(Map<String, Object> config);
}Register it during your onPrepare:
context.services().resolve(StorageRegistry.class)
.registerFactory(new MyCustomBackendFactory());Your factory's type() is the string users select in core.yml. Your factory's create() builds an unconnected backend instance — the registry calls connect() on it before handing it out.
platform-storage includes generic repository contracts you can use to abstract over storage in your own code. They aren't required — you can write directly against the backend handle — but they're useful when you want to swap implementations cleanly.
com.github.frosxt.prisoncore.storage.api.Repository<ID, AGGREGATE> — synchronous CRUD-ish surface (find, findAll, save, delete, exists, count).
com.github.frosxt.prisoncore.storage.api.AsyncRepository<ID, AGGREGATE> — same shape with CompletableFuture returns. Prefer this when called from the main thread.
com.github.frosxt.prisoncore.storage.api.DocumentStore<ID, D> — schemaless document store with predicate-based filtering. Lighter than Repository.
com.github.frosxt.prisoncore.storage.api.KeyValueStore<K, V> — minimal key/value contract. Use when you don't need queries.
com.github.frosxt.prisoncore.storage.api.LedgerStore<K, E> — append-only log of entries grouped by key. Use for transaction journals and audit trails.
com.github.frosxt.prisoncore.storage.core.AbstractCachingRepository<ID, AGGREGATE> — base class implementing Repository and AsyncRepository with a write-through in-memory cache. Subclass it and implement the four backend hooks (loadFromBackend, saveToBackend, deleteFromBackend, idOf).
These are tools, not requirements. If you'd rather write directly against Connection or MongoDatabase, that's fine.
You don't need to disconnect or shut down the backend. The kernel does that on its own during shutdown.
You do need to flush any in-memory state of your own before the kernel disconnects the backend. Cancel any scheduled flush task you have, then drain any pending writes synchronously in your onDisable:
@Override
protected void onDisable(final ModuleContext context) {
if (flushTask != null) {
flushTask.cancel();
}
myCache.flushSynchronously();
// backend disconnects automatically after all modules disable
}If you skip the synchronous flush and rely on the periodic task, you risk losing the writes that arrived in the last interval before shutdown.
This wiki documents the public API surface module authors are expected to touch. Kernel internals are intentionally omitted. Spot something wrong? Open an issue.
Start here
Architecture
Subsystems
- Commands
- Menus
- Messages and Placeholders
- Scheduler
- Storage
- Events and Listeners
- Configuration
- Player Profiles
Utilities