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
47 changes: 41 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[![License](https://img.shields.io/github/license/HauntedMC/dataprovider)](LICENSE)
[![Java 21](https://img.shields.io/badge/Java-21-007396)](https://adoptium.net/)

Build plugin features, not database plumbing.
Build plugins and services, not database plumbing.

`DataProvider` is shared infrastructure for plugin developers on Velocity and Bukkit/Paper.
It gives you one clean API for MySQL, MongoDB, Redis, and Redis messaging so your plugin code can stay focused on gameplay and business logic.
Expand All @@ -19,12 +19,11 @@ It gives you one clean API for MySQL, MongoDB, Redis, and Redis messaging so you
- Cleaner codebase: typed APIs reduce casting and repetitive boilerplate.
- Better runtime behavior: connection reuse and lifecycle cleanup are handled centrally.

## What You Get
## Features

- Unified backend support: `MYSQL`, `MONGODB`, `REDIS`, `REDIS_MESSAGING`
- Following data backends are implemented: `MYSQL`, `MONGODB`, `REDIS`, `REDIS_MESSAGING`
- Platform support: Velocity + Bukkit/Paper
- Reference-counted connection lifecycle management
- Optional ORM support for relational workflows (`ORMContext`)
- Optional ORM (through hibernate) support for relational workflows (`ORMContext`)

## Requirements

Expand All @@ -34,9 +33,32 @@ It gives you one clean API for MySQL, MongoDB, Redis, and Redis messaging so you

## Quick Start

Resolve the API from your platform runtime:

Velocity:

```java
DataProviderAPI api = VelocityDataProvider.getDataProviderAPI();
DataProviderAPI api = proxyServer.getPluginManager()
.getPlugin("dataprovider")
.flatMap(container -> container.getInstance()
.filter(DataProviderApiSupplier.class::isInstance)
.map(DataProviderApiSupplier.class::cast)
.map(DataProviderApiSupplier::dataProviderApi))
.orElseThrow(() -> new IllegalStateException("DataProvider is unavailable."));
```

Bukkit/Paper:

```java
RegisteredServiceProvider<DataProviderAPI> registration =
Bukkit.getServicesManager().getRegistration(DataProviderAPI.class);
if (registration == null) {
return;
}
DataProviderAPI api = registration.getProvider();
```

```java
Optional<RelationalDatabaseProvider> mysql = api.registerDatabaseAs(
DatabaseType.MYSQL,
"default",
Expand All @@ -53,6 +75,19 @@ api.unregisterDatabase(DatabaseType.MYSQL, "default");

If you maintain multiple plugins, this gives your team one standard integration model instead of backend-specific code per project.

## Admin Commands

- `/dataprovider help` shows command usage.
- `/dataprovider status [summary|connections] [unhealthy] [plugin <name>] [type <databaseType>]` shows active connection diagnostics.
- `/dataprovider config` prints current runtime config state (`orm.schema_mode` + backend enablement).
- `/dataprovider reload` reloads `config.yml` from disk.

Permissions:

- `dataprovider.command.status`
- `dataprovider.command.config`
- `dataprovider.command.reload`

## Install DataProvider (Server)

1. Build or download `DataProvider.jar`.
Expand Down
17 changes: 8 additions & 9 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@

## Supported Versions

Security fixes are generally provided for the latest stable release line.
Security fixes are prioritized for the latest stable release line.
Older versions may receive fixes on a best-effort basis.

| Version | Supported |
| --- | --- |
| Latest stable release | Yes |
| Older releases | Best effort / No guarantee |

## Reporting a Vulnerability

Expand All @@ -25,11 +22,13 @@ Include:
- Impact assessment
- Any proposed mitigation

## Response Expectations

- Initial triage acknowledgement: target within 72 hours
- Severity assessment and fix planning: as soon as reproducible
- Patch release timing: based on severity and exploitability
## What to Expect

- We acknowledge reports as quickly as practical.
- We validate impact, prioritize by severity, and prepare a fix.
- We coordinate disclosure after a fix or mitigation is available.


## Disclosure

Expand Down
16 changes: 15 additions & 1 deletion docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ Main modules:
- `api`: public registration/lookup surface
- `internal`: registry, factory, config mapping, identity, and lifecycle logic
- `database.*`: backend implementations and typed data-access contracts
- `platform.bukkit` / `platform.velocity`: platform bootstrap and caller context resolution
- `platform.internal`: shared platform runtime lifecycle and command behavior
- `platform.bukkit` / `platform.velocity`: platform adapters (bootstrap, command wiring, caller context resolution)

## Registration Model

Expand All @@ -30,9 +31,22 @@ Main modules:
## Lifecycle Safety

- Per-caller ownership checks gate unregister operations.
- Reference ownership is tracked by owner scope.
- Default API methods use plugin-level owner scope for predictable lifecycle behavior.
- If one plugin/software process multiplexes multiple components through one wrapper class, use optional scoped lifecycle facades (`DataProviderAPI.scope(...)`) to preserve component isolation.
- Explicit plugin-wide cleanup is available for shutdown flows that span multiple caller scopes.
- Stale/disconnected providers are evicted from registry lookup paths.
- Shutdown hooks unregister or stop backend resources cleanly.
- Bounded executors are used for asynchronous backend work queues.
- Platform runtime wrappers use a shared thread-safe lifecycle holder to prevent stale instance leaks across enable/disable cycles.

## Platform Layer Design

- `PlatformDataProviderRuntime` centralizes bootstrap shutdown behavior and startup rollback handling.
- Platform command adapters delegate to a shared `DataProviderCommandService` so Bukkit and Velocity command behavior stays identical.
- Command service exposes diagnostics-focused admin commands (`status`, `config`, `reload`) with permission-gated filtering and runtime health summaries.
- API discovery is platform-native: Bukkit registers `DataProviderAPI` in `ServicesManager`; Velocity exposes `DataProviderApiSupplier` on plugin instance.
- Platform-specific wrappers only map host APIs to shared internals (logger, command registration, event/plugin lifecycle hooks).

## ORM Integration

Expand Down
15 changes: 12 additions & 3 deletions docs/BEST_PRACTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,30 @@
## API usage

- Prefer `registerDatabaseOptional`, `registerDatabaseAs`, and `registerDataAccess` over raw nullable APIs.
- Treat returned `DatabaseProvider` instances as read-only handles; lifecycle is managed through the API.
- Prefer `getDataAccessOptional(...)` instead of manual casts.
- Treat database registration as startup wiring, not ad-hoc runtime behavior in hot paths.

## Lifecycle

- Register once during feature/plugin init.
- Register once during plugin/software startup.
- Unregister on disable.
- If you run multiple feature modules in one plugin, prefer releasing only the connections each feature acquired.
- Use `unregisterAllDatabases()` only when shutting down the entire plugin context.
- `registerDatabase(...)` / `unregisterAllDatabases()` use the default plugin-level owner scope.
- For full plugin/software shutdown across multiple scopes/classes, use `unregisterAllDatabasesForPlugin()`.

## Optional Scoped Ownership

- Use scoped ownership only when one plugin/software process has independently managed components.
- Create a scope facade from `DataProviderAPI.scope("component.name")`.
- Register and release through that scope object so ownership remains isolated.
- Keep scope naming stable and deterministic.

## Messaging

- Use one clear message class per channel contract.
- Keep channels stable and namespaced (for example: `proxy.staffchat.message`).
- Handle parse/dispatch failures as non-fatal.
- Keep handlers fast and non-blocking; use `security.max_queued_messages_per_handler` to cap per-handler backlog.

## ORM

Expand Down
48 changes: 41 additions & 7 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,55 +44,89 @@ databases:
- `require_secure_transport`
- `allow_public_key_retrieval`
- `pool_size`
- `min_idle`
- `queue_capacity`
- `connection_timeout_ms`
- `validation_timeout_ms`
- `idle_timeout_ms`
- `max_lifetime_ms`
- `leak_detection_threshold_ms`
- `connect_timeout_ms`
- `socket_timeout_ms`
- `query_timeout_seconds`
- `default_fetch_size`
- `cache_prepared_statements`
- `prepared_statement_cache_size`
- `prepared_statement_cache_sql_limit`

## MongoDB Keys (`databases/mongodb.yml`)

- `host`, `port`, `database`, `username`, `password`
- `authSource` (note exact casing)
- `require_secure_transport`
- `tls.enabled`
- `tls.allow_invalid_hostnames` (deprecated, ignored for security)
- `tls.trust_all_certificates` (deprecated, ignored for security)
- `tls.allow_invalid_hostnames` (must remain `false`; startup fails otherwise)
- `tls.trust_all_certificates` (must remain `false`; startup fails otherwise)
- `tls.trust_store_path` (optional JKS/PKCS12 path for private CA/self-managed trust)
- `tls.trust_store_password` (optional trust store password)
- `tls.trust_store_type` (optional, defaults to JVM `KeyStore.getDefaultType()`)
- `pool_size`
- `queue_capacity`
- `max_connection_pool_size`
- `min_connection_pool_size`
- `connect_timeout_ms`
- `socket_timeout_ms`
- `server_selection_timeout_ms`

## Redis Keys (`databases/redis.yml`)

- `host`, `port`, `user`, `password`, `database`
- `require_secure_transport`
- `tls.enabled`
- `tls.verify_hostname` (deprecated, ignored for security)
- `tls.trust_all_certificates` (deprecated, ignored for security)
- `tls.verify_hostname` (must remain `true`; startup fails otherwise)
- `tls.trust_all_certificates` (must remain `false`; startup fails otherwise)
- `tls.trust_store_path` (optional JKS/PKCS12 path for private CA/self-managed trust)
- `tls.trust_store_password` (optional trust store password)
- `tls.trust_store_type` (optional, defaults to JVM `KeyStore.getDefaultType()`)
- `pool.connections`
- `pool.threads`
- `queue_capacity`
- `pool.min_idle`
- `pool.max_idle`
- `pool.test_on_borrow`
- `pool.test_while_idle`
- `pool.queue_capacity`
- `connection_timeout_ms`
- `socket_timeout_ms`
- `scan_count`
- `security.max_scan_results`

## Redis Messaging Keys (`databases/redis_messaging.yml`)

- Same network + TLS fields as Redis key-value
- `pool.connections`
- `pool.threads`
- `pool.min_idle`
- `pool.max_idle`
- `pool.test_on_borrow`
- `pool.test_while_idle`
- `pool.queue_capacity`
- `pool.max_subscriptions`
- `connection_timeout_ms`
- `socket_timeout_ms`
- `security.max_payload_chars`
- `security.max_queued_messages_per_handler` (per-subscriber queue cap to isolate slow handlers)

## Common Mistakes

- Identifier mismatch between code and config section names
- Enabling TLS flags without server-side TLS support
- Using deprecated insecure TLS flags instead of a trust store for private CA deployments
- Assuming Redis and Redis Messaging use identical pool key paths (`queue_capacity` differs)
- Setting insecure TLS flags (`trust_all_certificates`, `allow_invalid_hostnames`, or `verify_hostname=false`) which now fail startup in 2.0
- Using `queue_capacity` at the root of `redis.yml` instead of `pool.queue_capacity`

## Operational Notes

- Use `default` for single-backend setups.
- Use explicit identifiers (for example `rw`, `ro`, `analytics`) for multi-backend setups.
- Validate trust store configuration in staging before production rollout.
- Never commit production credentials.
- During plugin shutdown across many classes/scopes, pair cleanup with `unregisterAllDatabasesForPlugin()`.
3 changes: 3 additions & 0 deletions docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ mvn -B package
- `src/main/java/nl/hauntedmc/dataprovider/api`: public API contracts
- `src/main/java/nl/hauntedmc/dataprovider/internal`: registry/factory/config internals
- `src/main/java/nl/hauntedmc/dataprovider/database`: backend implementations
- `src/main/java/nl/hauntedmc/dataprovider/logging`: backend-agnostic logging contracts + adapters
- `src/main/java/nl/hauntedmc/dataprovider/platform/internal`: shared platform lifecycle + command behavior
- `src/main/java/nl/hauntedmc/dataprovider/platform`: Bukkit/Velocity adapters
- `src/test/java`: unit tests by package

Expand All @@ -32,6 +34,7 @@ mvn -B package
- Keep connection registration in startup lifecycle paths, not request hot paths.
- Handle external IO failures as non-fatal where possible and log actionable context.
- Keep platform-specific integration (`platform.bukkit`, `platform.velocity`) thin and isolated.
- Put cross-platform wrapper behavior in `platform.internal` before adding platform-local duplication.
- Avoid leaking plugin context across module boundaries.

## Manual Validation Checklist
Expand Down
8 changes: 5 additions & 3 deletions docs/RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ Use `update_version.sh` to bump `major`, `minor`, or `patch`:

The script updates:

- `version.txt`
- `pom.xml`
- `README.md` (version examples)
- `pom.xml` (via Maven `versions:set`; source of truth)
- `src/main/java/nl/hauntedmc/dataprovider/platform/velocity/VelocityDataProvider.java`

Manual step:

- Update README dependency version examples if needed.

Then it commits and tags (`vX.Y.Z`) locally.
Push when ready:

Expand Down
52 changes: 52 additions & 0 deletions docs/SCOPED_LIFECYCLE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Scoped Lifecycle (Optional)

`DataProviderScope` is an advanced ownership option.
Use it only when one plugin/software process contains multiple independently managed components.

## Create a Scope

```java
DataProviderScope chatScope = api.scope("component.chat");
```

You can also use a typed scope object:

```java
OwnerScope chatOwner = OwnerScope.of("component.chat");
DataProviderScope chatScope = api.scope(chatOwner);
```

Scope names must be stable, non-blank, and use safe identifier characters.

## Register Through the Scope

```java
Optional<MessagingDataAccess> bus = chatScope.registerDataAccess(
DatabaseType.REDIS_MESSAGING,
"hauntedmc",
MessagingDataAccess.class
);
```

## Release Only That Scope

```java
chatScope.unregisterAllDatabases();
```

`DataProviderScope` is `AutoCloseable`, so it can also be used with try-with-resources:

```java
try (DataProviderScope tempScope = api.scope("component.temp")) {
tempScope.registerDatabase(DatabaseType.MYSQL, "default");
}
```

## Full Plugin/Process Shutdown

Scope cleanup is targeted.
For deterministic full shutdown across all scopes, call:

```java
api.unregisterAllDatabasesForPlugin();
```
Loading
Loading