Skip to content

Commit

Permalink
Load H2 library isolated
Browse files Browse the repository at this point in the history
- The H2 files generated by plugins weren't accessible with 3rd-party tools (database viewers and such) because it referenced classes from the plugin
- H2 now uses the raw H2 classes safely by loading into an isolated class loader (won't conflict with other plugins)
- Any `RuntimeLibrary` can be loaded isolated now
  • Loading branch information
srnyx committed Oct 17, 2024
1 parent f5335b8 commit 2f9d722
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 88 deletions.
113 changes: 113 additions & 0 deletions src/main/java/xyz/srnyx/annoyingapi/AnnoyingLibraryManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package xyz.srnyx.annoyingapi;

import net.byteflux.libby.BukkitLibraryManager;
import net.byteflux.libby.Library;
import net.byteflux.libby.classloader.IsolatedClassLoader;

import org.jetbrains.annotations.NotNull;

import xyz.srnyx.annoyingapi.parents.Annoyable;

import java.util.HashSet;
import java.util.Optional;
import java.util.Set;


/**
* A library manager that can load libraries into the server's classpath or into an isolated classloader using {@link RuntimeLibrary}
*/
public class AnnoyingLibraryManager extends BukkitLibraryManager implements Annoyable {
/**
* The {@link AnnoyingPlugin plugin} that this library manager is for
*/
@NotNull private final AnnoyingPlugin plugin;
/**
* Set of loaded {@link RuntimeLibrary libraries} that are <b>not</b> isolated
*/
@NotNull private final Set<RuntimeLibrary> loadedLibraries = new HashSet<>();

/**
* Create a new {@link AnnoyingLibraryManager} for the plugin
*
* @param plugin {@link #plugin}
*/
public AnnoyingLibraryManager(@NotNull AnnoyingPlugin plugin) {
super(plugin);
this.plugin = plugin;
}

/**
* Create a new {@link AnnoyingLibraryManager} for the plugin with a custom directory name
*
* @param plugin {@link #plugin}
* @param directoryName the name of the directory to store the libraries in
*/
public AnnoyingLibraryManager(@NotNull AnnoyingPlugin plugin, @NotNull String directoryName) {
super(plugin, directoryName);
this.plugin = plugin;
}

@Override @NotNull
public AnnoyingPlugin getAnnoyingPlugin() {
return plugin;
}

/**
* Load a {@link RuntimeLibrary} into the server's classpath or into an isolated classloader
* <br>If the library is isolated, the classloader is returned
*
* @param runtimeLibrary the library to load
*
* @return the classloader of the library if it is isolated
*/
@NotNull
public Optional<IsolatedClassLoader> loadLibrary(@NotNull RuntimeLibrary runtimeLibrary) {
for (final String repository : runtimeLibrary.repositories) addRepository(repository);
final Library library = runtimeLibrary.getLibrary(plugin);
loadLibrary(library);
if (!library.isIsolatedLoad()) {
loadedLibraries.add(runtimeLibrary);
return Optional.empty();
}
return getIsolatedClassLoaderOf(runtimeLibrary);
}

/**
* Get the {@link IsolatedClassLoader} of a {@link RuntimeLibrary}
*
* @param library the library to get the classloader of
*
* @return the classloader of the library
*/
@NotNull
public Optional<IsolatedClassLoader> getIsolatedClassLoaderOf(@NotNull RuntimeLibrary library) {
return Optional.ofNullable(getIsolatedClassLoaderOf(library.id));
}

/**
* Check if a {@link RuntimeLibrary} is loaded
*
* @param library the library to check
* @param isolated whether to check if the library is loaded into an isolated classloader (use {@link #getIsolatedClassLoaderOf(RuntimeLibrary)} to get the classloader)
*
* @return whether the library is loaded
*
* @see #isLoaded(RuntimeLibrary)
*/
public boolean isLoaded(@NotNull RuntimeLibrary library, boolean isolated) {
return isolated ? getIsolatedClassLoaderOf(library).isPresent() : loadedLibraries.contains(library);
}

/**
* Check if a {@link RuntimeLibrary} is loaded in the server's classpath
*
* @param library the library to check
*
* @return whether the library is loaded
*
* @see #isLoaded(RuntimeLibrary, boolean)
*/
public boolean isLoaded(@NotNull RuntimeLibrary library) {
return isLoaded(library, false);
}
}
22 changes: 6 additions & 16 deletions src/main/java/xyz/srnyx/annoyingapi/AnnoyingPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import me.clip.placeholderapi.PlaceholderAPI;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;

import net.byteflux.libby.BukkitLibraryManager;
import net.byteflux.libby.relocation.Relocation;

import org.bukkit.Bukkit;
Expand All @@ -24,7 +23,6 @@
import xyz.srnyx.annoyingapi.cooldown.CooldownManager;
import xyz.srnyx.annoyingapi.data.storage.ConnectionException;
import xyz.srnyx.annoyingapi.data.storage.DataManager;
import xyz.srnyx.annoyingapi.data.ItemData;
import xyz.srnyx.annoyingapi.data.storage.StorageConfig;
import xyz.srnyx.annoyingapi.data.storage.dialects.sql.SQLDialect;
import xyz.srnyx.annoyingapi.dependency.AnnoyingDependency;
Expand All @@ -43,7 +41,6 @@
import java.lang.reflect.Modifier;
import java.sql.SQLException;
import java.util.*;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
Expand Down Expand Up @@ -76,16 +73,9 @@ public void log(@NotNull LogRecord logRecord) {
*/
@NotNull public final AnnoyingOptions options = AnnoyingOptions.load(getResource("plugin.yml"));
/**
* The {@link BukkitLibraryManager} for the plugin
* The {@link AnnoyingLibraryManager} for the plugin to manage {@link RuntimeLibrary libraries}
*/
@NotNull public final BukkitLibraryManager libraryManager = new BukkitLibraryManager(this, "libs");
/**
* Set of loaded {@link RuntimeLibrary libraries}
* <br>If a library's files are manually deleted (or something else goes wrong), the plugin will not notice this change until the library is {@link RuntimeLibrary#load(AnnoyingPlugin) loaded again} (usually requires a server restart)
* <br>This should really only be used if you want to do a basic check if a library is loaded or not based on what features the API is currently using
* <br>Something like {@link ItemData#attemptItemNbtApi(Supplier)} may be a better method when using a runtime library
*/
@NotNull public final Set<RuntimeLibrary> loadedLibraries = new HashSet<>();
@NotNull public final AnnoyingLibraryManager libraryManager = new AnnoyingLibraryManager(this, "libs");
/**
* Wrapper for bStats
*/
Expand Down Expand Up @@ -248,8 +238,8 @@ private void enablePlugin() {

// Enable bStats
if (new AnnoyingResource(this, options.bStatsOptions.fileName, options.bStatsOptions.fileOptions).getBoolean(options.bStatsOptions.toggleKey)) {
RuntimeLibrary.BSTATS_BASE.load(this);
RuntimeLibrary.BSTATS_BUKKIT.load(this);
libraryManager.loadLibrary(RuntimeLibrary.BSTATS_BASE);
libraryManager.loadLibrary(RuntimeLibrary.BSTATS_BUKKIT);
stats = new AnnoyingStats(this);
}

Expand Down Expand Up @@ -286,8 +276,8 @@ private void enablePlugin() {
final Set<String> packages = options.registrationOptions.automaticRegistration.packages;
if (!packages.isEmpty()) {
// Load Javassist and Reflections libraries
RuntimeLibrary.JAVASSIST.load(this);
RuntimeLibrary.REFLECTIONS.load(this);
libraryManager.loadLibrary(RuntimeLibrary.JAVASSIST);
libraryManager.loadLibrary(RuntimeLibrary.REFLECTIONS);

// Register classes
final Set<Class<? extends Registrable>> ignoredClasses = options.registrationOptions.automaticRegistration.ignoredClasses;
Expand Down
75 changes: 33 additions & 42 deletions src/main/java/xyz/srnyx/annoyingapi/RuntimeLibrary.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.google.common.collect.ImmutableSortedSet;

import net.byteflux.libby.Library;
import net.byteflux.libby.LibraryManager;

import org.jetbrains.annotations.NotNull;

Expand All @@ -18,116 +17,108 @@
*/
public enum RuntimeLibrary {
/**
* org.bstats:bstats-base
* {@code org.bstats:bstats-base}
*/
BSTATS_BASE("https://repo1.maven.org/maven2/", plugin -> Library.builder()
.groupId("org{}bstats")
.artifactId("bstats-base")
.version("3.1.0")
.relocate(plugin.getRelocation("org{}bstats")).build()),
.relocate(plugin.getRelocation("org{}bstats"))),
/**
* org.bstats:bstats-bukkit
* {@code org.bstats:bstats-bukkit}
*/
BSTATS_BUKKIT("https://repo1.maven.org/maven2/", plugin -> Library.builder()
.groupId("org{}bstats")
.artifactId("bstats-bukkit")
.version("3.1.0")
.relocate(plugin.getRelocation("org{}bstats")).build()),
.relocate(plugin.getRelocation("org{}bstats"))),
/**
* org.javassist:javassist
* {@code org.javassist:javassist}
*/
JAVASSIST("https://repo1.maven.org/maven2/", plugin -> Library.builder()
.groupId("org{}javassist")
.artifactId("javassist")
.version("3.28.0-GA")
.relocate(plugin.getRelocation("javassist{}", "javassist{}")).build()),
.relocate(plugin.getRelocation("javassist{}", "javassist{}"))),
/**
* org.reflections:reflections
* {@code org.reflections:reflections}
*/
REFLECTIONS("https://repo1.maven.org/maven2/", plugin -> Library.builder()
.groupId("org{}reflections")
.artifactId("reflections")
.version("0.10.2")
.relocate(plugin.getRelocation("javassist{}", "javassist{}"))
.relocate(plugin.getRelocation("org{}reflections")).build()),
.relocate(plugin.getRelocation("org{}reflections"))),
/**
* de.tr7zw:item-nbt-api
* {@code de.tr7zw:item-nbt-api}
*/
ITEM_NBT_API("https://repo.codemc.org/repository/maven-public/", plugin -> Library.builder()
.groupId("de{}tr7zw")
.artifactId("item-nbt-api")
.version("2.13.2")
.relocate(plugin.getRelocation("de{}tr7zw{}changeme{}nbtapi")).build()),
.relocate(plugin.getRelocation("de{}tr7zw{}changeme{}nbtapi"))),
/**
* com.h2database:h2
* {@code com.h2database:h2}
* <br>Isolated load by default
*/
H2("https://repo1.maven.org/maven2/", plugin -> Library.builder()
.groupId("com{}h2database")
.artifactId("h2")
.version("2.2.220")
.relocate(plugin.getRelocation("org{}h2")).build()),
.isolatedLoad(true)),
/**
* org.postgresql:postgresql
* {@code org.postgresql:postgresql}
*/
POSTGRESQL("https://repo1.maven.org/maven2/", plugin -> Library.builder()
.groupId("org{}postgresql")
.artifactId("postgresql")
.version("42.7.3")
.relocate(plugin.getRelocation("org{}postgresql")).build());
.relocate(plugin.getRelocation("org{}postgresql")));

/**
* The unique ID of the library (used for identification with {@link AnnoyingLibraryManager#getIsolatedClassLoaderOf(RuntimeLibrary)})
*/
@NotNull public final String id;
/**
* The repositories to add before loading the library (immutable)
*/
@NotNull public final SortedSet<String> repositories;
/**
* The library to load
* The builder to create the library with
*/
@NotNull public final Function<AnnoyingPlugin, Library> library;
@NotNull public final Function<AnnoyingPlugin, Library.Builder> libraryBuilder;

/**
* Creates a new {@link RuntimeLibrary}
*
* @param repositories {@link #repositories}
* @param library {@link #library}
* @param libraryBuilder {@link #libraryBuilder}
*/
RuntimeLibrary(@NotNull Collection<String> repositories, @NotNull Function<AnnoyingPlugin, Library> library) {
RuntimeLibrary(@NotNull Collection<String> repositories, @NotNull Function<AnnoyingPlugin, Library.Builder> libraryBuilder) {
this.id = getClass().getSimpleName() + "." + name();
this.repositories = ImmutableSortedSet.copyOf(repositories);
this.library = library;
this.libraryBuilder = plugin -> libraryBuilder.apply(plugin).id(id);
}

/**
* Creates a new {@link RuntimeLibrary} with a single repository
*
* @param repository the repository to add
* @param library the library to load
* @param repository {@link #repositories}
* @param libraryBuilder {@link #libraryBuilder}
*/
RuntimeLibrary(@NotNull String repository, @NotNull Function<AnnoyingPlugin, Library> library) {
this.repositories = ImmutableSortedSet.of(repository);
this.library = library;
RuntimeLibrary(@NotNull String repository, @NotNull Function<AnnoyingPlugin, Library.Builder> libraryBuilder) {
this(ImmutableSortedSet.of(repository), libraryBuilder);
}

/**
* Gets the library to load
* Builds and returns the library to load from the {@link #libraryBuilder}
*
* @param plugin the plugin to load the library into
* @param plugin the plugin to build the library with
*
* @return the library to load
* @return the library to build/get
*/
@NotNull
public Library getLibrary(@NotNull AnnoyingPlugin plugin) {
return library.apply(plugin);
}

/**
* Downloads and loads the library into the specified plugin
*
* @param plugin the plugin to load the library into
*/
public void load(@NotNull AnnoyingPlugin plugin) {
if (plugin.loadedLibraries.contains(this)) return;
final LibraryManager manager = plugin.libraryManager;
for (final String repository : repositories) manager.addRepository(repository);
manager.loadLibrary(getLibrary(plugin));
plugin.loadedLibraries.add(this);
return libraryBuilder.apply(plugin).build();
}
}
4 changes: 2 additions & 2 deletions src/main/java/xyz/srnyx/annoyingapi/data/ItemData.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public ItemData(@NotNull AnnoyingPlugin plugin, @NotNull ItemStack item) {
*/
@NotNull
public Optional<String> attemptItemNbtApi(@NotNull Supplier<String> supplier) {
RuntimeLibrary.ITEM_NBT_API.load(plugin);
plugin.libraryManager.loadLibrary(RuntimeLibrary.ITEM_NBT_API);
return Optional.ofNullable(supplier.get());
}

Expand All @@ -60,7 +60,7 @@ public Optional<String> attemptItemNbtApi(@NotNull Supplier<String> supplier) {
* @param runnable the runnable to attempt
*/
public void attemptItemNbtApi(@NotNull Runnable runnable) {
RuntimeLibrary.ITEM_NBT_API.load(plugin);
plugin.libraryManager.loadLibrary(RuntimeLibrary.ITEM_NBT_API);
runnable.run();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public DataManager attemptDatabaseMigration() {
final File dataFolder = plugin.getDataFolder();
final File storageNew = new File(dataFolder, "storage-new.yml");
if (!storageNew.exists()) return this;
AnnoyingPlugin.log(Level.INFO, "&aFound &2storage-new.yml&a, attempting to migrate data from &2storage.yml&a to &2storage-new.yml&a...");
AnnoyingPlugin.log(Level.WARNING, "&aSuccessfully found &2storage-new.yml&a, attempting to migrate data from &2storage.yml&a to &2storage-new.yml&a...");

// NEW: Connect to new database
final AnnoyingFile<?> storageNewFile = new AnnoyingFile<>(plugin, storageNew, new AnnoyingFile.Options<>().canBeEmpty(false));
Expand All @@ -110,7 +110,7 @@ public DataManager attemptDatabaseMigration() {
for (final Map.Entry<String, Map<String, String>> entry1 : entry.getValue().entrySet()) AnnoyingPlugin.log(Level.SEVERE, storageConfig.migrationLogPrefix + "Failed to set values for &4" + entry1.getKey() + "&c in table &4" + table + "&c: &4" + entry1.getValue());
}
} else {
AnnoyingPlugin.log(Level.WARNING, storageConfig.migrationLogPrefix + "Found no data to migrate! This may or may not be an error...");
AnnoyingPlugin.log(Level.SEVERE, storageConfig.migrationLogPrefix + "Found no data to migrate! This may or may not be an error...");
}

// OLD: Close old connection
Expand Down Expand Up @@ -139,7 +139,7 @@ public DataManager attemptDatabaseMigration() {
}

// NEW: Use new storage
AnnoyingPlugin.log(Level.INFO, "&aFinished migrating data from &2storage.yml&a to &2storage-new.yml&a!");
AnnoyingPlugin.log(Level.WARNING, "&aSuccessfully finished migrating data from &2storage.yml&a to &2storage-new.yml&a!");
return newManager;
}
}
Loading

0 comments on commit 2f9d722

Please sign in to comment.