Problem creating API with ServicesManager

SKJOLBERG

New member
Aug 28, 2022
4
0
1
Spain
github.com
Hi :),

I have asked in Discord without luck, it is something complex and there is little information in google, I have searched several websites and docs for information about it and I can't find anything either, I have been several days without sleeping well just thinking about this, I have only looked at plugins like Vault that applies this interface to create its API, the problem is the following.

I have an API which I have an interface that I'm using to test if this works, the interface is called "TestClazzImpl", then in my main plugin I have a class called "TestClazz" where I apply and get a version (a stupid example, it's just for testing) and where I instantiate with the interface, there I register the interface as a service.

The problem is that when I get the service in the other plugin that wants to use my API the value it returns is null, instead if I use the following it returns the active services of the plugin where only one active service appears which is the interface "TestClazzImpl":

Java:
Plugin sbr = Bukkit.getPluginManager().getPlugin("SimpleBlockRegen");
List<RegisteredServiceProvider<?>> list = getServer().getServicesManager().getRegistrations(sbr);

So I don't understand why getting the services by passing the plugin instance parameter works but getting the service by passing the interface class parameter doesn't work.

I make softdepend in plugin.yml. It is the first time that I work with ServicesManager and it may be difficult for me to understand some things, but for the moment I understand what I have explained, I only have this problem that I don't know what it is due to.


In my plugin it returns the service and I get this in the console:

YAML:
[18:08:34 INFO]: [SimpleBlockRegen] [STDOUT] [org.bukkit.plugin.RegisteredServiceProvider@6d937b49]

The other plugin tells me that it is empty and returns null. Thanks and I am attentive to anyone who wants to help me.


Code:

My plugin:

Java:
public class SimpleBlockRegen extends JavaPlugin {

    @Getter
    private static SimpleBlockRegen plugin;
    private MainModule mainModule;
    private TestClazzImpl testClazz;
    private ServicesManager sm;

    @Override
    public void onEnable() {
        SimpleBlockRegen.plugin = this;
        mainModule = new MainModule(this);
        mainModule.load();


        this.testClazz = new TestClazz();
        this.sm = getServer().getServicesManager();

        sm.register(TestClazzImpl.class, this.testClazz, plugin, ServicePriority.Normal);

        Collection<RegisteredServiceProvider<TestClazzImpl>> a = getServer().getServicesManager().getRegistrations(TestClazzImpl.class);
        if(!a.isEmpty()){
            for (RegisteredServiceProvider provider : a) {
                System.out.println("Service: " + provider.getService());
            }
        } else {
            System.out.println("Empty");
        }

        testClazz.setVersion("0.2.0");
        System.out.println("Version: " + testClazz.getVersion());

    }

    @Override
    public void onDisable() {
        mainModule.unload();
    }

}


My other plugin:

Java:
public class SimpleDropInventory extends JavaPlugin {

    @Getter
    static SimpleDropInventory plugin;
    private MainModule mainModule;
    private TestClazzImpl testClazz;

    @Override
    public void onEnable() {
        mainModule = new MainModule(this);
        mainModule.load();

        this.testClazz = getProvider(TestClazzImpl.class);
       
        Collection<RegisteredServiceProvider<TestClazzImpl>> a = getServer().getServicesManager().getRegistrations(TestClazzImpl.class);
        if(!a.isEmpty()){
            for (RegisteredServiceProvider provider : a) {
                System.out.println("Service:" + provider.getService());
            }
            System.out.println("Version: " + testClazz.getVersion());
        } else {
            System.out.println("Empty");
        }


    }

    private <T> T getProvider(Class<T> classz) {
        RegisteredServiceProvider<T> provider = getServer().getServicesManager().getRegistration(classz);
        if (provider == null){
            System.out.println("Null");
            return null;
        }
        System.out.println("No null");
        return provider.getProvider();
    }

    @Override
    public void onDisable() {
        mainModule.unload();
    }

}

Docs: https://jd.papermc.io/paper/1.16/org/bukkit/plugin/ServicesManager.html
 

electronicboy

Administrator
Staff member
Dec 11, 2021
216
10
34
28
make sure that you're not shading the interface you're trying to get into both jars, otherwise they'll both have their own class which would ofc break the identity in there
 

SKJOLBERG

New member
Aug 28, 2022
4
0
1
Spain
github.com
Hi electronicboy,

That's what I was told it could be, but as far as I know I don't shade the API dependency, I've attached the build.gradle.kts I use below, I've also downloaded a decompiler to see the location of the packages in the final jar and they match.


I have tested this in the other plugin to actually see if the service is registering:

Java:
Plugin sbr = Bukkit.getPluginManager().getPlugin("SimpleBlockRegen");
List<RegisteredServiceProvider<?>> list = getServer().getServicesManager().getRegistrations(sbr);
for (RegisteredServiceProvider<?> li : list) {
    System.out.println("Service: " + li.getService());
}
this.testClazz = getProvider(TestClazzImpl.class);


The result is as follows:

YAML:
[12:14:46 INFO]: [SimpleBlockRegen] Enabling SimpleBlockRegen v0.2.0-SNAPSHOT
[12:14:47 INFO]: [SimpleBlockRegen] [STDOUT] Service: org.bukkit.plugin.RegisteredServiceProvider@4e48ca5f
[12:14:47 INFO]: [SimpleBlockRegen] [STDOUT] Version: 0.2.0
[12:14:47 INFO]: [SimpleDropInventory] Enabling SimpleDropInventory v0.0.9-SNAPSHOT
[12:14:47 INFO]: [SimpleDropInventory] [STDOUT] Service: interface net.shibacraft.simpleblockregen.api.TestClazzImpl
[12:14:47 INFO]: [SimpleDropInventory] [STDOUT] Null

My main plugin has successfully registered the service and is apparently active, but my other plugin cannot get it.

These are the packages when I build the jar.​


My pl:

mipl.png

My other pl:

otro pl.png


As far as can be seen they match and are not shading each other.

Build.gradle.kts​


My pl:

Java:
plugins {
    id("java")
    id("com.github.johnrengelman.shadow") version ("7.1.2")
}

subprojects  {
    plugins.apply("java")
    plugins.apply("com.github.johnrengelman.shadow")

    group = "${project.property("group")}" // net.shibacraft
    version = "${project.property("version")}"

    repositories {
        mavenLocal()
        mavenCentral()
        maven("https://repo.papermc.io/repository/maven-public/")
        maven("https://jitpack.io/")
        maven("https://maven.enginehub.org/repo/")
        maven("https://repo.alessiodp.com/releases/")
        maven("https://repo.glaremasters.me/repository/towny/")
        maven("https://repo.glaremasters.me/repository/bloodshot/")
        maven("https://repo.extendedclip.com/content/repositories/placeholderapi/")
        maven("https://mvn.lumine.io/repository/maven-public/")
        maven("https://nexus.phoenixdvpt.fr/repository/maven-public/")
    }

    dependencies {
        implementation("com.github.Skjolberg:ShibacraftAPI:0.0.3") // My library
        implementation("com.github.Skjolberg:SimpleBlockRegenAPI:0.2.0-R12") // My API
    }

    tasks {
        compileJava {
            options.release.set(8)
        }

        assemble {
            dependsOn(shadowJar)
        }

        withType<JavaCompile> {
            options.encoding = "UTF-8"
        }

        shadowJar {
            minimize()
        }
    }

}

configurations.all {
    resolutionStrategy.cacheChangingModulesFor(0, "seconds")
}

tasks.withType<JavaCompile>().configureEach {
    options.isIncremental = true
    options.isFork = true
}

tasks {
    java {
        toolchain {
            languageVersion.set(JavaLanguageVersion.of(8))
        }
    }

    shadowJar {
        arrayOf("adapt", "adapt:v1_8_R3", "adapt:v1_9_R4", "adapt:v1_12_R2", "adapt:v1_13_R2", "adapt:v1_17_R1",
            "adapt:v1_18_R2", "common", "plugin").forEach {
            val buildTask = project(":$it").tasks.named("shadowJar")
            dependsOn(buildTask)
            from(zipTree(buildTask.map {out -> out.outputs.files.singleFile}))
        }
        archiveFileName.set("${project.name}-${project.version}.jar")
    }
}


My other pl:

Java:
plugins {
    java
    id("com.github.johnrengelman.shadow") version ("7.1.2")
}

val libs = "net.shibacraft.simpledropinventory.api.libs"

group = "net.shibacraft"
version = "0.0.9-SNAPSHOT"

configurations.all {
    resolutionStrategy.cacheChangingModulesFor(0, "seconds")
}

repositories {
    mavenLocal()
    mavenCentral()
    maven("https://repo.papermc.io/repository/maven-public/")
    maven("https://repo.unnamed.team/repository/unnamed-public/")
    maven("https://jitpack.io")
}

dependencies {
    implementation("com.github.Skjolberg:SimpleBlockRegenAPI:0.2.0-R12") // My API
    implementation("com.github.simplix-softworks:simplixstorage:3.2.4")
    implementation("org.bstats:bstats-bukkit:3.0.0")
    implementation("me.fixeddev:commandflow-bukkit:0.5.2")
    compileOnly("io.papermc.paper:paper-api:1.19.2-R0.1-SNAPSHOT")
    compileOnly("org.projectlombok:lombok:1.18.24")
    annotationProcessor("org.projectlombok:lombok:1.18.24")
}

tasks {
    java {
        toolchain {
            languageVersion.set(JavaLanguageVersion.of(8))
        }
    }
    withType<JavaCompile> {
        options.encoding = "UTF-8"
    }
    shadowJar {
        archiveClassifier.set("")
        archiveFileName.set("${project.name}-${project.version}.jar")
        relocate("me.fixeddev.commandflow", "$libs.cmdFlow")
        relocate("net.kyori.text", "$libs.kyori")
        relocate("org.bstats", "$libs.bStats")
        relocate("de.leonhard.storage", "$libs.leonhardStorage")
        relocate("org.checkerframework", "$libs.jetbrains")
        relocate("org.jetbrains.annotations", "$libs.jetbrains")
        minimize()
    }
    build {
        dependsOn(shadowJar)
    }
    processResources {
        filesMatching("plugin.yml") {
            expand("v" to project.version)
        }
    }

}

Is it something to do with using modules? it's the first time I work with modules, this is the build.gradle.kts that I use to compile the project, that is, the build.gradle.kts that is in the main project directory, in the others I don't shadow dependencies, I only compile dependencies.
 

electronicboy

Administrator
Staff member
Dec 11, 2021
216
10
34
28
You have the exact same class in both plugins, which means that those will both be loaded under their own identities, and thus any lookup will fail, as although the classes have the exact same name, they're still two different classes

If you wanna build against something without shading it, set it to compileOnly, rather than implementation; or setup an exclude for it
 

SKJOLBERG

New member
Aug 28, 2022
4
0
1
Spain
github.com
I use libby (library) to download other dependencies at runtime, this downloads a .jar in the /lib directory of the plugin with the compiled library, I have set compileOnly and it still doesn't work, is it something to do with using modules in my main plugin?
 

electronicboy

Administrator
Staff member
Dec 11, 2021
216
10
34
28
with the compiled library

if you mean, your 2nd plugin refers to plugin 1 as a library, and thus pulls it's jar, yes, that's going to induce the very issue I keep saying about; If you want the service manager to work across 2 plugins, the 2nd plugin cannot have a copy of classes from the first plugin, otherwise you're going to have two entirely separate different class identities; the JVM allows multiple classloaders to define a class, just, those classes will never ==, and attempting to cross refer them will fail any identity/instanceof comparisons, and induce a linkage/casting error in many cases

It's pretty hard to say from the outside exactly how you're setup looks, but this purely looks like a classpath issue and you having multiple copies of the same
 

SKJOLBERG

New member
Aug 28, 2022
4
0
1
Spain
github.com
My English is not perfect, I think what you say is not what I am doing, the API has the interfaces that both the first plugin and the second plugin use, I register the API interface in the first plugin as a service and then in the second I try to get the service, the class (interface) only exists in the API.

Today I was given access to MavenCentral to publish the API in case that helps. Maybe the problem is that the API is automatically downloaded in a .jar at runtime and I need to get it from MavenCentral in both plugins? Is that what you mean? Sorry again for my English electronicboy, sometimes the barrier makes it difficult to understand each other :(