Apple Ptr
Apple Ptr

Reputation: 11

SpiRawSqlService not found setting up EBean in a Spigot plugin

Overview

I set up a github repo for this question to provide as much of the boiled down environment as possible.

My goal is to set up ebean ORM for database manangement from a Paper Minecraft plugin. I'm able to shade in the ebean dependencies, but creating a query using "io.ebean:ebean-querybean:" throws an error saying that it cannot find an implementation of SpiRawSqlService.

Environment

Paper Minecraft: paper-1.19.3-367.jar
Java 18
Ebean enhancement plugin for IntelliJ(I checked that I have it enabled for this project)
io.ebean gradle plugin version 13.10.0
shadowJar gradle plugin version 7.1.2

The Stacktrace

Everything is fine setting up the database, and saving to the Database. Queries without using a querybean work fine as well. The error is thrown when initializing any class containing a reference to a generated querybean.
The error outputted is printed the latest.log
Caused by: java.lang.IllegalStateException: No service implementation found for interface org.example.ebean.io.ebean.service.SpiRawSqlService

The stacktrace tells us that it couldn't find org.example.ebean.io.ebean.service.SpiRawSqlService.
Looking at the decompiled shadowJar after package relocation, the implementation for this class is found at org.example.ebean.io.ebeaninternal.server.rawsql.DRawSql;

Printing out the ClassLoader#getDefinedPackages on the instance supplied when creating the ebean Database connection results in this:

org.example.ebean
org.example.ebean.database
org.example.ebean.io.ebean
org.example.ebean.io.ebean.annotation
org.example.ebean.io.ebean.config
org.example.ebean.io.ebean.config.dbplatform
org.example.ebean.io.ebean.datasource
org.example.ebean.io.ebean.meta

As you can see, the org.example.ebean.io.ebeaninternal package and subpackages are not outputted in this list.

Underlying issue

How/where is the package "ebeaninternal" being loaded if at all? How can I get the enhanced querybean to find this package so it can load the implementation (DRawSql) of SpiRawSqlService?

Upvotes: 1

Views: 228

Answers (1)

Apple Ptr
Apple Ptr

Reputation: 11

Reasoning

Bukkit's @EventHandler utilizes a different contextClassLoader than the ClassLoader that loads the ebean classes/services (contained in the ShadowJar).

The error states No service implementation found because the thread that is initializing the querybean does not have access to that class.

Explained Solution

The solution here is to use Thread#setContextClassLoader() to use the same ClassLoader used when calling DatabaseFactory.createWithContextClassLoader(). Set the ClassLoader, initialize every Class that uses a QueryBean, revert the ClassLoader to what it originally was.

EBean might be able to solve this problem. But for now, a fix is to just call an empty init method on every class that becomes an Enhanced-QueryBean from a thread that is using the proper ContextClassLoader

Full Example

I pushed the full example containing the fix to the original github repo

Basic Example

DatabaseSetup.java


    public static void load() {
        DataSourceConfig dataSourceConfig = configureDataSource();
        DatabaseConfig dbConfig = configureDatabase(dataSourceConfig);

        // We should use the classloader that loaded this plugin
        // because this plugin has our ebean dependencies
        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
        ClassLoader pluginClassLoader = BukkitEBeanPlugin.class.getClassLoader();

        // create the DatabaseFactory with the classloader containing ebean dependencies
        DatabaseFactory.createWithContextClassLoader(dbConfig, pluginClassLoader);

        // Set the current thread's contextClassLoader to the classLoader with the ebean dependencies
        // This allows the class to initialize itself with access to the required class dependencies
        Thread.currentThread().setContextClassLoader(pluginClassLoader);

        // invoke the static initialization of every class that contains a querybean.
        // Note that any method in the class will initialize the class.
        FindByQueryBean.init();

        // Restore the contextClassLoader to what it was originally
        Thread.currentThread().setContextClassLoader(originalClassLoader);

        BukkitEBeanPlugin.get().getLogger().info("Successfully created database");
    }
    ...
}

FindByQueryBean.java

public static void init() {
    // intentionally empty
}

Upvotes: 0

Related Questions