ObsiCore is a Spigot 1.8.8 plugin development framework, It provides a complete infrastructure: dependency injection, SQL migrations, cache, commands, listeners, GUI and seeders — all auto-discovered via package scanning.
Requirements: Java 8, Maven, Spigot 1.8.8
Add ObsiCore as a Maven dependency in your plugin:
<dependency>
<groupId>fr.kainovaii</groupId>
<artifactId>ObsiCore</artifactId>
<version>latest</version>
<scope>provided</scope>
</dependency>In your plugin's onEnable():
@Override
public void onEnable() {
DB.initMySQL("localhost", 3306, "mydb", "user", "password", getLogger());
new CoreBootstrap(getLogger(), "fr.kainovaii.myplugin", this)
.dbDriver(new MyDatabaseDriver())
.withMigration("my_migrations")
.boot();
}boot() automatically runs in order:
- Cache initialization
- SQL migrations
- DI component scan (
@Service,@Repository) - Listener scan (
@EventListener) - Command scan (
@Command) - Seeder scan (
@SeederInfo) - GUI initialization (
@GUI)
Package: fr.kainovaii.core
Main entry point for the framework. Orchestrates the startup of all subsystems via a fluent builder.
| Method | Description |
|---|---|
CoreBootstrap(Logger, String, Plugin) |
Constructor. Takes the logger, the base package to scan, and the plugin instance. |
dbDriver(DatabaseDriver) |
Sets the database driver to use. |
withMigration(String) |
Enables migrations with a custom table name. |
withMigration() |
Enables migrations with the default table "migrations". |
cacheDriver(CacheDriver) |
Sets the cache driver (default: InMemoryCacheDriver). |
boot() |
Launches all startup phases in order. |
Example:
public class MyDatabaseDriver implements DatabaseDriver {
@Override
public <T> T withConnection(Callable<T> task) { return DB.getInstance().executeWithConnection(task); }
@Override
public <T> T withTransaction(Callable<T> task) { return DB.getInstance().executeWithTransaction(task); }
}
new CoreBootstrap(getLogger(), "fr.kainovaii.myplugin", this)
.dbDriver(new MyDatabaseDriver())
.withMigration("plugin_migrations")
.cacheDriver(new RedisCacheDriver(jedisPool))
.boot();Package: fr.kainovaii.core.cache
Static facade for accessing the cache system. The driver is automatically injected by CoreBootstrap.
| Method | Description |
|---|---|
put(String key, Object value, int ttlSeconds) |
Stores a value with a TTL. |
putAll(Map<String,Object> entries, int ttlSeconds) |
Stores multiple values with a shared TTL. |
get(String key) |
Retrieves a value (returns null if absent/expired). |
getAll(List<String> keys) |
Retrieves multiple values in key order. |
has(String key) |
Checks if a key exists and has not expired. |
forget(String key) |
Removes an entry from the cache. |
remember(String key, int ttl, Supplier<T> supplier) |
Returns the cached value, or computes, stores and returns it. |
setDriver(CacheDriver) |
(internal) Sets the active driver. |
Example:
Player player = Cache.remember("player:" + uuid, 300, () -> loadFromDB(uuid));Package: fr.kainovaii.core.cache
Contract to implement for providing a custom cache backend. Methods: put, putAll, get, getAll, has, forget.
Package: fr.kainovaii.core.cache
Cache driver configuration.
| Constructor | Description |
|---|---|
CacheConfig() |
In-Memory configuration (default). |
CacheConfig(String driver, String host, int port, String password) |
Redis configuration. Pass "redis" as driver to enable Redis. |
| Method | Description |
|---|---|
isRedis() |
Returns true if the configured driver is Redis. |
getRedisHost() |
Returns the Redis host. |
getRedisPort() |
Returns the Redis port. |
getRedisPassword() |
Returns the Redis password (or null). |
Package: fr.kainovaii.core.cache.drivers
In-Memory implementation based on ConcurrentHashMap with TTL expiration. Default driver if none is configured.
Package: fr.kainovaii.core.cache.drivers
Redis implementation via JedisPool. Batch operations use pipelining for performance.
| Constructor | Description |
|---|---|
RedisCacheDriver(JedisPool pool) |
Creates the driver with an existing Jedis connection pool. |
Package: fr.kainovaii.core.database
MySQL connection manager with HikariCP pool. Thread-safe singleton.
| Method | Description |
|---|---|
DB.initMySQL(host, port, database, user, password, logger) |
(static) Initializes the singleton. Call only once. |
DB.getInstance() |
(static) Returns the singleton instance. |
DB.withConnection(Callable<T>) |
(static) Shortcut to getInstance().executeWithConnection(). |
DB.withTransaction(Callable<T>) |
(static) Shortcut to getInstance().executeWithTransaction(). |
executeWithConnection(Callable<T>) |
(instance) Opens a connection if needed, executes the task, closes. |
executeWithTransaction(Callable<T>) |
(instance) Opens a transaction, commits on success, rolls back on error. |
close() |
Closes the connection pool. Call in onDisable(). |
getDatabase() |
Returns the configured database name. |
Example:
DB.initMySQL("localhost", 3306, "mydb", "root", "password", getLogger());
DB.withConnection(() -> {
return null;
});Package: fr.kainovaii.core.database
Interface the plugin must implement to provide DB access to the framework. Methods: withConnection(Callable<T>), withTransaction(Callable<T>).
Package: fr.kainovaii.core.database
Base class for migrations. Provides a fluent Laravel-style API for schema definition.
| Method | Description |
|---|---|
up() (abstract) |
Migration logic (schema creation/modification). |
createTable(String name, TableBuilder) |
Creates a table with columns defined in the builder. |
dropTable(String name) |
Drops a table if it exists. |
addColumn(String table, String column, String definition) |
Adds a column to an existing table. |
dropColumn(String table, String column) |
Drops a column. |
tableExists(String name) |
Checks if a table exists. |
Migration.Blueprint — Fluent column builder:
| Method | Generated SQL |
|---|---|
id() / id(name) |
INT AUTO_INCREMENT PRIMARY KEY |
string(name) / string(name, length) |
VARCHAR(255) or VARCHAR(n) |
text(name) |
TEXT |
integer(name) |
INT |
bigInteger(name) |
BIGINT |
decimal(name, precision, scale) |
DECIMAL(p,s) |
bool(name) |
BOOLEAN |
date(name) |
DATE |
dateTime(name) |
DATETIME |
timestamp(name) |
TIMESTAMP |
timestamps() |
Automatic created_at + updated_at |
notNull() |
Adds NOT NULL to the last column |
unique() |
Adds UNIQUE |
defaultValue(String) |
Adds DEFAULT value |
nullable() |
No-op (nullable by default) |
index() |
Adds an INDEX on the last column |
Example:
@MigrationInfo(version = 1, name = "create_players_table")
public class CreatePlayersTable extends Migration {
@Override
public void up() {
createTable("players", t -> t
.id()
.string("uuid", 36).notNull().unique()
.string("name").notNull()
.integer("kills").defaultValue("0")
.timestamps()
);
}
}Package: fr.kainovaii.core.database
Discovers and runs all pending migrations. Idempotent: an already-applied migration is skipped.
| Constructor | Description |
|---|---|
MigrationRunner(Logger, String packageName, DatabaseDriver) |
With default tracking table ("migrations"). |
MigrationRunner(Logger, String packageName, DatabaseDriver, String table) |
With custom tracking table. |
run() |
Launches the scan and execution of pending migrations. |
Package: fr.kainovaii.core.database.annotations
Class annotation to mark a migration.
| Attribute | Description |
|---|---|
version |
Version number. Migrations are executed in ascending order. |
name |
Descriptive name (e.g., "create_users_table"). |
Package: fr.kainovaii.core.database.seeder
Base class for database seeders.
| Method | Description |
|---|---|
run() (abstract) |
Data insertion logic. |
setLogger(Logger) |
Injects the logger (called by SeederScanner). |
Package: fr.kainovaii.core.database.seeder
Scans and runs seeders annotated with @SeederInfo, in the order defined by order. Each seeder runs only once per session.
| Method | Description |
|---|---|
scan() |
Launches the scan and execution. |
reset() |
Resets tracking to allow re-execution. |
Package: fr.kainovaii.core.database.seeder.annotations
Class annotation to mark a seeder.
| Attribute | Description |
|---|---|
name |
Unique identifier for the seeder. |
order |
Execution order (default: 100, lower = first). |
Package: fr.kainovaii.core.di
Singleton IoC container. Manages automatic dependency resolution via constructor injection and field injection.
| Method | Description |
|---|---|
Container.singleton(Class<T>, T instance) |
Manually registers a singleton instance. |
Container.bind(Class<T>, Class<? extends T>) |
Binds an interface to its implementation. |
Container.resolve(Class<T>) |
Resolves and returns an instance with auto-injected dependencies. |
Container.injectFields(Object) |
Injects the @Inject fields of an existing instance. |
Container.clear() |
Clears all singletons and bindings (useful for tests). |
Resolution rules:
- First checks if a singleton is already registered.
- Classes must be annotated with
@Serviceor@Repository(or bound viabind()). - Detects circular dependencies and throws an explicit exception.
- If multiple constructors exist, one must be annotated with
@Inject.
Package: fr.kainovaii.core.di
Scans the base package for classes annotated with @Service or @Repository and registers them in the Container via Container.resolve().
Package: fr.kainovaii.core.di.annotations
Marks a class as an injectable service. Will be auto-discovered and instantiated as a singleton by ComponentScanner.
Package: fr.kainovaii.core.di.annotations
Marks a class as an injectable repository (data access layer). Identical behavior to @Service.
Package: fr.kainovaii.core.di.annotations
Marks a constructor or field as an injection point. The Container will automatically inject the corresponding dependency.
Example:
@Service
public class PlayerService {
@Inject
private PlayerRepository playerRepository;
public void save(PlayerData data) {
DB.withTransaction(() -> {
return null;
});
}
}Package: fr.kainovaii.core.command
Base class for all command handlers. Provides automatic routing to @Default and @Subcommand methods.
On instantiation, automatically injects @Inject fields via the Container and scans annotated methods.
Parameter injection in methods: Parameters of type CommandSender, Player, String[], or String are automatically injected at invocation.
Example:
@Command(name = "stats", aliases = {"s"}, description = "View stats")
public class StatsCommand extends BaseCommand {
@Inject
private PlayerService playerService;
@Default
public void onDefault(Player player) {
player.sendMessage("Your stats: ...");
}
@Subcommand("top")
public void onTop(CommandSender sender) {
sender.sendMessage("Top players: ...");
}
}Package: fr.kainovaii.core.command
Bridge between BaseCommand and Bukkit's Command system. Created and registered automatically by CommandScanner. No direct usage.
Package: fr.kainovaii.core.command
Scans classes annotated with @Command, instantiates each handler, and dynamically registers them in Bukkit's CommandMap via reflection.
Package: fr.kainovaii.core.command.annotations
Class annotation to mark a command handler.
| Attribute | Description |
|---|---|
name |
Command name (e.g., "stats"). |
aliases |
Aliases (e.g., {"s", "stat"}). Default: {}. |
description |
Description. Default: "". |
usage |
Usage message. Default: "". |
permission |
Required permission. Default: "". |
Package: fr.kainovaii.core.command.annotations
Method annotation to define a sub-handler.
| Attribute | Description |
|---|---|
value |
Subcommand name (e.g., "create"). |
aliases |
Subcommand aliases. |
permission |
Permission required for this subcommand. |
Package: fr.kainovaii.core.command.annotations
Method annotation. The annotated method is called when no subcommand matches.
Package: fr.kainovaii.core.events
Scans classes annotated with @EventListener that implement Bukkit Listener, instantiates them, injects their @Inject fields, and registers them with Bukkit.
Package: fr.kainovaii.core.events.annotations
Marks a class as a Bukkit event listener. The class must implement org.bukkit.event.Listener.
Example:
@EventListener
public class PlayerJoinListener implements Listener {
@Inject
private PlayerService playerService;
@EventHandler
public void onJoin(PlayerJoinEvent e) {
playerService.onPlayerJoin(e.getPlayer());
}
}Package: fr.kainovaii.core.inventory
Abstract base class for custom GUI inventories. Implements InventoryHolder. Subclasses define content in init(), which is called automatically after @Inject field injection.
Constructors:
| Constructor | Description |
|---|---|
Inventory(int size) |
CHEST inventory with the given number of slots and Bukkit's default title. |
Inventory(int size, String title) |
CHEST inventory with custom size and title. |
Inventory(InventoryType type) |
Inventory of the given Bukkit type with its default title. |
Inventory(InventoryType type, String title) |
Inventory of the given Bukkit type with a custom title. |
Main methods:
| Method | Description |
|---|---|
init() (abstract) |
Inventory population and handler definition. Called once after DI injection. |
open(Player) |
Opens the GUI. If the player already has an inventory of the same type/size open, updates the content in place (avoids visual flicker), otherwise opens normally. |
addItem(ItemStack) |
Adds an item to the first empty slot. |
addItem(ItemStack, Consumer<InventoryClickEvent>) |
Adds an item to the first empty slot with a click handler. |
setItem(int slot, ItemStack) |
Places an item on a slot without a handler. |
setItem(int slot, ItemStack, Consumer<InventoryClickEvent>) |
Places an item on a slot with a click handler. |
setItems(int from, int to, ItemStack) |
Places the same item on a contiguous range of slots. |
setItems(int from, int to, ItemStack, Consumer<InventoryClickEvent>) |
Same with shared handler. |
setItems(int[] slots, ItemStack) |
Places the same item on the given slots. |
setItems(int[] slots, ItemStack, Consumer<InventoryClickEvent>) |
Same with shared handler. |
removeItem(int slot) |
Removes the item and its handler from the given slot. |
removeItems(int... slots) |
Removes items and handlers from the given slots. |
setCloseFilter(Predicate<Player>) |
Prevents closing if the predicate returns true (automatically reopens on the next tick). |
addOpenHandler(Consumer<InventoryOpenEvent>) |
Adds an open handler. |
addCloseHandler(Consumer<InventoryCloseEvent>) |
Adds a close handler. |
addClickHandler(Consumer<InventoryClickEvent>) |
Adds a global click handler (all slots). |
getBorders() |
Returns the slot indices of the border. |
getCorners() |
Returns the corner indices. |
onOpen(InventoryOpenEvent) |
(protected) Hook to override for custom open logic. |
onClick(InventoryClickEvent) |
(protected) Hook to override for custom click logic. |
onClose(InventoryCloseEvent) |
(protected) Hook to override for custom close logic. |
Example:
@GUI
public class ShopGUI extends Inventory {
public ShopGUI() {
super(54, "§6Shop");
}
@Override
public void init() {
fillBorder(new ItemBuilder(Material.STAINED_GLASS_PANE).data(7).name("§r").build());
setItem(13, new ItemBuilder(Material.DIAMOND).name("§bDiamond §7- 100 coins").build(),
e -> ((Player) e.getWhoClicked()).sendMessage("Purchase complete!"));
}
}
new ShopGUI().open(player);Package: fr.kainovaii.core.inventory
Static utility that registers the central Bukkit listener routing all inventory events to Inventory instances. Called automatically by CoreBootstrap.
| Method | Description |
|---|---|
register(Plugin) |
Registers the listener. Can only be called once. |
closeAll() |
Closes all GUI inventories open by connected players. |
InventoryManager.InventoryListener — Internal listener that routes InventoryClickEvent, InventoryOpenEvent, InventoryCloseEvent to the correct handlers, and manages PluginDisableEvent.
Package: fr.kainovaii.core.inventory
Scans classes annotated with @GUI extending Inventory. Does not instantiate them — GUIs are created on demand. Validates and logs discovered GUIs at startup.
Package: fr.kainovaii.core.inventory.annotations
Marks a class as a GUI inventory. The class must extend Inventory. Will be discovered by GUIScanner at startup.
Package: fr.kainovaii.core.inventory
Fluent builder for constructing ItemStacks cleanly.
| Method | Description |
|---|---|
ItemBuilder(Material) |
Creates a builder for an item of a given material. |
ItemBuilder(ItemStack) |
Creates a builder from an existing item. |
type(Material) |
Changes the material. |
data(int) |
Sets the data value (sub-type). |
durability(short) |
Sets the durability. |
amount(int) |
Sets the quantity (1–64). |
enchant(Enchantment) |
Adds an enchantment at level 1. |
enchant(Enchantment, int) |
Adds an enchantment at a given level. |
removeEnchant(Enchantment) |
Removes an enchantment. |
removeEnchants() |
Removes all enchantments. |
meta(Consumer<ItemMeta>) |
Direct meta modification. |
meta(Class<T>, Consumer<T>) |
Typed meta modification (e.g., LeatherArmorMeta). |
name(String) |
Sets the display name (supports § color codes). |
lore(String...) |
Replaces the lore with the given lines. |
lore(List<String>) |
Replaces the lore with a list. |
addLore(String) / addLore(String...) |
Appends lines to the existing lore. |
flags(ItemFlag...) |
Adds flags (e.g., HIDE_ENCHANTS). |
flags() |
Adds all available flags. |
removeFlags(ItemFlag...) |
Removes flags. |
armorColor(Color) |
Sets the color of leather armor. |
build() |
Returns the final ItemStack. |
Package: fr.kainovaii.core.scoreboard
Abstract base class for custom scoreboards. Subclasses define sidebar lines and tablist content.
Auto-refreshed by ScoreboardManager at a configurable interval.
The Bukkit Scoreboard and Objective are created once and reused across refreshes — only changed lines are updated in-place.
| Method | Description |
|---|---|
getLines(Player) (abstract) |
Returns the sidebar lines for the given player. Max 15 lines, color codes (§) supported. |
getTitleFrames(String baseTitle) |
Returns animation frames for the title. Override to customize. Only called when animated = true. |
getTabHeader(Player) |
Tab list header. Return null or "" to skip. Default: null. |
getTabFooter(Player) |
Tab list footer. Return null or "" to skip. Default: null. |
remove(Player) |
Removes the scoreboard from the player and resets them to the main server scoreboard. |
Example:
@Scoreboard(title = "§6§lMy Server", interval = 20)
public class MyScoreboard extends BaseScoreboard {
@Inject
private PlayerService playerService;
@Override
public List<String> getLines(Player player) {
return Arrays.asList(
"§8§m ",
"§7➤ §fPlayer",
"§8 • §7Name : §f" + player.getName(),
"§8§m "
);
}
@Override
public String getTabHeader(Player player) {
return "§6§lMy Server";
}
@Override
public String getTabFooter(Player player) {
return "§7Online: §a" + Bukkit.getOnlinePlayers().size();
}
}Package: fr.kainovaii.core.scoreboard
Manages scoreboard assignment per player. Runs two separate schedulers per player:
- Data task — refreshes lines at
@Scoreboard#interval()ticks (default: 20) - Title task — advances the animation frame every 3 ticks when
animated = true
This separation ensures DB queries never run on animation frames.
| Method | Description |
|---|---|
ScoreboardManager.init(Plugin, Logger) |
(static) Initializes the singleton. Called automatically by CoreBootstrap. |
ScoreboardManager.assign(Player, BaseScoreboard) |
(static) Assigns a scoreboard to a player and starts refresh tasks. |
ScoreboardManager.remove(Player) |
(static) Removes the scoreboard from a player and cancels all tasks. |
ScoreboardManager.refresh(Player) |
(static) Manually triggers a data refresh for a player. |
ScoreboardManager.shutdown() |
(static) Removes all scoreboards and cancels all tasks. Call in onDisable(). |
Example:
// In PlayerJoinListener
@EventHandler
public void onJoin(PlayerJoinEvent e) {
MyScoreboard board = new MyScoreboard();
Container.injectFields(board); // inject @Inject fields
ScoreboardManager.assign(e.getPlayer(), board);
}
// In PlayerQuitListener
@EventHandler
public void onQuit(PlayerQuitEvent e) {
ScoreboardManager.remove(e.getPlayer());
}
// In onDisable()
ScoreboardManager.shutdown();Package: fr.kainovaii.core.scoreboard
Scans the base package for classes annotated with @Scoreboard that extend BaseScoreboard.
Does not instantiate them — scoreboards are created on demand via ScoreboardManager.assign().
Validates and logs discovered scoreboard classes at startup. Called automatically by CoreBootstrap.
Package: fr.kainovaii.core.scoreboard.annotations
Class annotation to mark a scoreboard definition. The class must extend BaseScoreboard.
| Attribute | Description |
|---|---|
title |
Sidebar title displayed above the lines. Supports color codes (§). Default: "§fScoreboard". |
interval |
Auto-refresh interval in ticks (20 ticks = 1 second). Set to 0 to disable automatic refresh. Default: 20. |
animated |
Enables title animation by cycling through getTitleFrames() every 3 ticks. Default: false. |
Package: fr.kainovaii.core.scanner
Generic abstract class for annotation-based class discovery via reflection. Base of all framework scanners.
| Method | Description |
|---|---|
scan() |
Starts the discovery, filtering, and processing of classes. |
discoverClasses() |
Discovers all classes matching the scan criteria. |
processClass(Class<? extends T>) (abstract) |
Processing of a discovered class. |
getBaseClass() (abstract) |
Base type to scan (e.g., Listener.class). |
getAnnotationClass() (abstract) |
Primary filter annotation. |
getAnnotationClasses() |
List of filter annotations (default: delegates to getAnnotationClass()). |
getScannerName() (abstract) |
Scanner name for logs. |
shouldProcess(Class<? extends T>) |
Determines if a class should be processed: must be concrete, non-interface, and carry at least one of the annotations defined in getAnnotationClasses(). |
getComparator() |
Sort comparator for classes (default: alphabetical). |
onScanComplete() |
Hook called after all processing. |
Package: fr.kainovaii.core.utils
Chat message formatting utility with aligned header/footer and tree structure support.
Created via ChatBox.Builder (builder pattern).
ChatBox.Builder:
| Method | Description |
|---|---|
title(String) |
Title displayed in the header (supports color codes). |
line(String) |
Adds a line. |
lines(String...) |
Adds multiple lines. |
treeLine(String content, boolean isLast) |
Adds a line with a tree prefix (┌, ├, └). |
width(int) |
Target chat box width (default: 32). |
build() |
Builds the ChatBox instance. |
| Instance method | Description |
|---|---|
send(Player) |
Sends the formatted chat box to the player. |
Example:
ChatBox.builder()
.title("§bStats")
.treeLine("§7Kills: §a" + kills, false)
.treeLine("§7Deaths: §c" + deaths, false)
.treeLine("§7K/D: §e" + kd, true)
.build()
.send(player);Package: fr.kainovaii.core.utils
Serialization/deserialization of ItemStack lists to Base64 for storage in databases or files.
| Method | Description |
|---|---|
serializeItems(List<ItemStack>) |
Serializes a list of items to a Base64 string. |
deserializeItems(String) |
Deserializes a Base64 string to a list of ItemStacks. |
Example:
String encoded = ItemSerializer.serializeItems(Arrays.asList(player.getInventory().getContents()));
DB.withConnection(() -> { return null; });
List<ItemStack> items = ItemSerializer.deserializeItems(encoded);| Dependency | Version | Scope |
|---|---|---|
| Spigot API | 1.8.8-R0.1-SNAPSHOT | provided |
| ActiveJDBC | 2.3.1-j8 | provided |
| HikariCP | 2.7.9 | provided |
| Jedis (Redis) | 5.1.0 | provided |
| Reflections | 0.10.2 | compile |
provideddependencies must be present on the server or shaded into the final jar by the consuming plugin.
Developed by KainoVaii — ObsiCore v1.0