Skip to content

Commit

Permalink
Implement Sponge support (#99) + Substantial refactoring
Browse files Browse the repository at this point in the history
* Closes #99
* Generate event listeners by manual method scan when running on
  Sponge API 9. Using the standard approach, registration fails
  due use of an isolated classloader. See
  SpongePowered/Sponge#3747
* Some refactoring. Use '.plugin' package for plugin entrypoints.
  Define SCM in main pom; add codecov profile. Use futures in
  EnvEnforcer, EnvUserResolver: this may alter the thread context
  of some command executions.
* Remove Java 8 detection entirely. LibertyBans never was a
  popular plugin, so there's no need to cater to those who forget
  to read. More importantly, Java 8 usage dwindles and many other
  plugins now require newer Java versions.
* Add relocation protection. We now serve and appease users who
  suffer unrelocated libraries. However, the real motivation is
  to support Sponge, which is warranted in its inclusion of an
  older version of HikariCP.
* Refactor bootstrap loggers and avoid depending on slf4j in
  bans-bootstrap.
* Extract executable jar without relying on plugin jar file.
      The Sponge API does not provide a method to access a
  plugin's jar file. Neither does it allow dynamically scanning
  plugin resources. As such, including directories of jars inside
  the executable distribution as used to occur requires us to
  squeeze from rock the Path to our plugin jar while running on
  Sponge or else manually list each dependency jar.
      To solve this, the directory of dependencies belonging to a
  dependency bundle is now zipped as its own jar, after which the
  launcher performs double-extraction on the twice-nested jars.
  With this solution, adding dependencies to bans-core remains
  trivial, requiring only the XML dependency declaration.
* Update contribution guide, CONTRIBUTING.md, with code style
  basics and information on addons.
  • Loading branch information
A248 committed Sep 10, 2022
1 parent 48bfdcc commit c041502
Show file tree
Hide file tree
Showing 145 changed files with 4,928 additions and 2,078 deletions.
52 changes: 41 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,38 @@ You can use any IDE you choose. Simply import the project and ensure it is confi

The project is split into several Maven modules. You will want to make sure that your IDE recognizes these modules.

## Code Formatting

Please use tabs. Otherwise, try to follow the surrounding code style.

Try to avoid nesting when working with `CompletableFuture`. If you place callbacks on a new line, the indentation becomes extreme. For example, prefer this:

```java
return selector.getActivePunishmentById(id).thenCompose((optPunishment) -> {
// Callback
});
```

To this:

```java
return selector.getApplicablePunishment(id)
.thenCompose((optPunishment) -> {
// Callback
// Notice the extra indentation
});
```

If necessary, break the arguments to the method creating the future onto a new line:

```java
return selector.getApplicablePunishment(
uuid, address, PunishmentType.BAN
).thenCompose((optPunishment) -> {
// Callback
});
```

## Architecture

### Project Structure
Expand All @@ -36,6 +68,7 @@ The project is split into several Maven modules. You will want to make sure that
* Platform-specific plugins:
* `bans-env-bungeeplugin` (extends Plugin)
* `bans-env-spigotplugin` (extends JavaPlugin)
* `bans-env-spongeplugin` (@Plugin)
* `bans-env-velocityplugin` (@Plugin)

The following modules comprise the core implementation:
Expand All @@ -44,6 +77,7 @@ The following modules comprise the core implementation:
* Platform-specific implementation code:
* `bans-env-bungee`
* `bans-env-spigot`
* `bans-env-sponge`
* `bans-env-velocity`

### Startup Process
Expand Down Expand Up @@ -71,6 +105,12 @@ Config Database bans-env-velocity

The implementation modules are placed in an isolated classloader. This classloader separation means that plugin classes are visible to implementation classes, but implementation classes are *not* visible to plugin classes.

### Addons

Addon modules, under `bans-core-addons`, may be installed at user preference.

Installed addon jars are loaded by the isolated classloader, functioning as if part of `bans-core`.

## Distribution

LibertyBans is distributed in two ways.
Expand All @@ -79,20 +119,10 @@ LibertyBans is distributed in two ways.

The release distribution is a lightweight jar which downloads its dependencies at runtime, with SHA-512 hash verification. This jar is published to SpigotMC and Github Releases.

### The development distribution (for compiling and running from source)
### The development distribution

The development distribution is intended for compiling and running from source. It uses a nested jar format and extracts these jars at runtime.

### Relocation in Other Plugins

Other plugins must relocate their dependencies for LibertyBans to work properly.

Sometimes, the user's server is bugged -- another plugin did not relocate its dependencies properly. This happens most commonly with HikariCP, a widely-used library.

When this happens, we print a massive warning message and identify the offending plugin.
* For development builds, we fail-fast with an error message.
* For release builds, we attempt to proceed, but we can make no guarantees that LibertyBans will function properly.

## Testing

You are encouraged to write unit and integration tests!
Expand Down
26 changes: 1 addition & 25 deletions bans-bootstrap/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,37 +70,13 @@
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>--add-modules org.slf4j</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<argLine>--add-modules org.slf4j</argLine>
</configuration>
</plugin>
</plugins>
</build>

<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</dependency>
<!-- Not guaranteed to be present in all environments -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@

public final class PluginInfo {

public static final String ANNOTE_ID = "${plugin.annotationId}";
public static final String ID = "${plugin.id}";
public static final String NAME = "${plugin.name}";
public static final String VERSION = "${plugin.version}";
public static final String AUTHOR = "${plugin.author}";
public static final String DESCRIPTION = "${plugin.description}";
public static final String URL = "${plugin.url}";

Expand Down
1 change: 0 additions & 1 deletion bans-bootstrap/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
module space.arim.libertybans.bootstrap {
requires static java.logging;
requires static org.slf4j;
exports space.arim.libertybans.bootstrap;
exports space.arim.libertybans.bootstrap.logger;
exports space.arim.libertybans.bootstrap.plugin;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* LibertyBans
* Copyright © 2022 Anand Beh
*
* LibertyBans is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* LibertyBans is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with LibertyBans. If not, see <https://www.gnu.org/licenses/>
* and navigate to version 3 of the GNU Affero General Public License.
*/

package space.arim.libertybans.bootstrap;

import space.arim.libertybans.bootstrap.depend.BootstrapException;
import space.arim.libertybans.bootstrap.depend.JarAttachment;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;

final class AttachableClassLoader extends URLClassLoader implements JarAttachment {

static {
ClassLoader.registerAsParallelCapable();
}

AttachableClassLoader(String classLoaderName, ClassLoader parent) {
super(classLoaderName, new URL[] {}, parent);
}

@Override
public void addJarPath(Path jarFile) {
URL url;
try {
url = jarFile.toUri().toURL();
} catch (MalformedURLException ex) {
throw new BootstrapException("Unable to convert Path to URL", ex);
}
addURL(url);
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
/*
* LibertyBans-bootstrap
* Copyright © 2020 Anand Beh <https://www.arim.space>
*
* LibertyBans-bootstrap is free software: you can redistribute it and/or modify
/*
* LibertyBans
* Copyright © 2022 Anand Beh
*
* LibertyBans is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* LibertyBans-bootstrap is distributed in the hope that it will be useful,
*
* LibertyBans is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with LibertyBans-bootstrap. If not, see <https://www.gnu.org/licenses/>
* along with LibertyBans. If not, see <https://www.gnu.org/licenses/>
* and navigate to version 3 of the GNU Affero General Public License.
*/

package space.arim.libertybans.bootstrap;

public interface BaseFoundation {
Expand All @@ -28,4 +29,11 @@ public interface BaseFoundation {

void shutdown();

/**
* Used for Sponge only; since Sponge requires early command registration and service provision
*
* @return the platform accessors
*/
Object platformAccess();

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@

import space.arim.libertybans.bootstrap.depend.Dependency;
import space.arim.libertybans.bootstrap.depend.DependencyLoaderBuilder;
import space.arim.libertybans.bootstrap.depend.DownloadProcessor;
import space.arim.libertybans.bootstrap.depend.JarWithinJarDownloadProcessor;
import space.arim.libertybans.bootstrap.depend.ExistingDependency;
import space.arim.libertybans.bootstrap.depend.ExtractNestedJars;
import space.arim.libertybans.bootstrap.depend.Repository;

import java.io.BufferedReader;
Expand All @@ -48,7 +48,7 @@ enum DependencyBundle {

private final Repository repository;

private DependencyBundle(Repository repository) {
DependencyBundle(Repository repository) {
this.repository = repository;
}

Expand All @@ -57,13 +57,13 @@ public String toString() {
return name().toLowerCase(Locale.ROOT).replace("_", "-");
}

DownloadProcessor existingFileProcessor() {
/*
Avoid unnecessarily copying jars if the file name does not contain "SNAPSHOT" -
release dependencies are not supposed to change
*/
return new JarWithinJarDownloadProcessor("jars/" + this)
.replaceExisting((jarName) -> jarName.contains("SNAPSHOT"));
ExistingDependency existingDependency() {
String jarResourceName = this + "-bundle.jar";
URL jarResource = getClass().getResource("/dependencies/jars/" + jarResourceName);
if (jarResource == null) {
throw new IllegalStateException("Cannot find nested jar resource for " + this);
}
return new ExtractNestedJars(jarResource, jarResourceName);
}

void prepareToDownload(DependencyLoaderBuilder loader) {
Expand Down Expand Up @@ -119,8 +119,8 @@ private Dependency dependencyFrom(BlockingQueue<String> readDetails) {
throw malformatted("Lacking details, received only " + readDetails);
}
return Dependency.of(
readDetails.remove(), readDetails.remove(), readDetails.remove(), readDetails.remove(),
DownloadProcessor.simple());
readDetails.remove(), readDetails.remove(), readDetails.remove(), readDetails.remove()
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* LibertyBans
* Copyright © 2022 Anand Beh
*
* LibertyBans is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* LibertyBans is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with LibertyBans. If not, see <https://www.gnu.org/licenses/>
* and navigate to version 3 of the GNU Affero General Public License.
*/

package space.arim.libertybans.bootstrap;

import java.util.Set;

final class FilteringClassLoader extends ClassLoader {

private static final ClassNotFoundException FAILED_FILTER;
private static final ClassNotFoundException DOES_NOT_LOAD_CLASSES;

static {
ClassLoader.registerAsParallelCapable();
FAILED_FILTER = new ClassNotFoundException(
"Class is filtered from the eyes of child classloaders");
DOES_NOT_LOAD_CLASSES = new ClassNotFoundException(
FilteringClassLoader.class.getName() + " does not itself load classes");
}

private final Set<ProtectedLibrary> protectedLibraries;

FilteringClassLoader(ClassLoader parent, Set<ProtectedLibrary> protectedLibraries) {
super(parent);
this.protectedLibraries = Set.copyOf(protectedLibraries);
}

@Override
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
for (ProtectedLibrary protectedLibrary : protectedLibraries) {
if (className.startsWith(protectedLibrary.basePackage())) {
throw FAILED_FILTER;
}
}
return super.loadClass(className, resolve);
}

@Override
public Class<?> findClass(String className) throws ClassNotFoundException {
throw DOES_NOT_LOAD_CLASSES;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,9 @@ public <P> BaseFoundation invoke(Class<P> pluginType, P plugin, Path folder)
return clazz.getDeclaredConstructor(pluginType, Path.class).newInstance(plugin, folder).launch();
}

public <P, S> BaseFoundation invoke(Class<P> pluginType, P plugin, Class<S> serverType, S server, Path folder)
throws ReflectiveOperationException, IllegalArgumentException, SecurityException {
return clazz.getDeclaredConstructor(pluginType, serverType, Path.class).newInstance(plugin, server, folder).launch();
}

}
Loading

0 comments on commit c041502

Please sign in to comment.