diff --git a/.gitignore b/.gitignore index 375ba08..2f98ae1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /.idea/ +/.idea/codeStyles/codeStyleConfig.xml *.iml /target/ /src/main/resources/build.properties diff --git a/build.gradle b/build.gradle index 379ad64..29e9ccb 100644 --- a/build.gradle +++ b/build.gradle @@ -32,6 +32,12 @@ dependencies { library "org.mariadb.jdbc:mariadb-java-client:3.0.4" library "org.apache.httpcomponents:httpclient:4.5.13" library "org.apache.commons:commons-lang3:3.12.0" + library "org.apache.maven.resolver:maven-resolver-api:1.7.3" + library "org.apache.maven.resolver:maven-resolver-impl:1.7.3" + library "org.apache.maven.resolver:maven-resolver-connector-basic:1.7.3" + library "org.apache.maven.resolver:maven-resolver-transport-http:1.7.3" + library "org.apache.maven:maven-resolver-provider:3.8.5" + library "org.springframework.boot:spring-boot-starter-web:2.6.5" compileOnly "io.papermc.paper:paper-api:1.18.2-R0.1-SNAPSHOT" implementation "org.bstats:bstats-base:3.0.0" implementation "org.bstats:bstats-bukkit:3.0.0" diff --git a/src/main/java/dev/plex/admin/AdminList.java b/src/main/java/dev/plex/admin/AdminList.java index e6db31c..806e5e8 100644 --- a/src/main/java/dev/plex/admin/AdminList.java +++ b/src/main/java/dev/plex/admin/AdminList.java @@ -2,12 +2,15 @@ package dev.plex.admin; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; import dev.morphia.Datastore; import dev.morphia.query.Query; import dev.plex.PlexBase; import dev.plex.player.PlexPlayer; import dev.plex.rank.enums.Rank; import dev.plex.storage.StorageType; + import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -23,8 +26,7 @@ import java.util.stream.Collectors; * @see Admin */ -public class AdminList extends PlexBase -{ +public class AdminList extends PlexBase { /** * Key/Value storage, where the key is the unique ID of the admin */ @@ -35,8 +37,7 @@ public class AdminList extends PlexBase * * @param admin The admin object */ - public void addToCache(Admin admin) - { + public void addToCache(Admin admin) { admins.put(admin.getUuid(), admin); } @@ -46,8 +47,7 @@ public class AdminList extends PlexBase * @param uuid The unique ID of the admin * @see UUID */ - public void removeFromCache(UUID uuid) - { + public void removeFromCache(UUID uuid) { admins.remove(uuid); } @@ -56,35 +56,76 @@ public class AdminList extends PlexBase * * @return An array list of the names of every admin */ - public List getAllAdmins() - { + public List getAllAdmins() { List admins = Lists.newArrayList(); - if (plugin.getStorageType() == StorageType.MONGODB) - { + if (plugin.getStorageType() == StorageType.MONGODB) { Datastore store = plugin.getMongoConnection().getDatastore(); Query query = store.find(PlexPlayer.class); admins.addAll(query.stream().filter(plexPlayer -> plexPlayer.getRankFromString().isAtLeast(Rank.ADMIN)).map(PlexPlayer::getName).collect(Collectors.toList())); - } - else - { - try (Connection con = plugin.getSqlConnection().getCon()) - { + } else { + try (Connection con = plugin.getSqlConnection().getCon()) { PreparedStatement statement = con.prepareStatement("SELECT * FROM `players` WHERE rank IN(?, ?, ?)"); statement.setString(1, Rank.ADMIN.name().toLowerCase()); statement.setString(2, Rank.SENIOR_ADMIN.name().toLowerCase()); statement.setString(3, Rank.EXECUTIVE.name().toLowerCase()); ResultSet set = statement.executeQuery(); - while (set.next()) - { + while (set.next()) { admins.add(set.getString("name")); } - } - catch (SQLException throwables) - { + } catch (SQLException throwables) { throwables.printStackTrace(); } } return admins; } + + /** + * Gathers every admin (cached and databsed) + * + * @return An array list of the names of every admin + */ + public List getAllAdminPlayers() { + List plexPlayers = Lists.newArrayList(); + if (plugin.getStorageType() == StorageType.MONGODB) { + Datastore store = plugin.getMongoConnection().getDatastore(); + Query query = store.find(PlexPlayer.class); + return query.stream().toList().stream().filter(player -> plugin.getRankManager().isAdmin(player)).collect(Collectors.toList()); + } else { + try (Connection con = plugin.getSqlConnection().getCon()) { + PreparedStatement statement = con.prepareStatement("SELECT * FROM `players` WHERE rank IN(?, ?, ?)"); + statement.setString(1, Rank.ADMIN.name().toLowerCase()); + statement.setString(2, Rank.SENIOR_ADMIN.name().toLowerCase()); + statement.setString(3, Rank.EXECUTIVE.name().toLowerCase()); + + ResultSet set = statement.executeQuery(); + while (set.next()) { + String uuid = set.getString("uuid"); + String name = set.getString("name"); + String loginMSG = set.getString("login_msg"); + String prefix = set.getString("prefix"); + String rankName = set.getString("rank").toUpperCase(); + long coins = set.getLong("coins"); + boolean vanished = set.getBoolean("vanished"); + boolean commandspy = set.getBoolean("commandspy"); + List ips = new Gson().fromJson(set.getString("ips"), new TypeToken>() { + }.getType()); + + PlexPlayer plexPlayer = new PlexPlayer(UUID.fromString(uuid)); + plexPlayer.setName(name); + plexPlayer.setLoginMessage(loginMSG); + plexPlayer.setPrefix(prefix); + plexPlayer.setRank(rankName); + plexPlayer.setIps(ips); + plexPlayer.setCoins(coins); + plexPlayer.setVanished(vanished); + plexPlayer.setCommandSpy(commandspy); + plexPlayers.add(plexPlayer); + } + } catch (SQLException throwables) { + throwables.printStackTrace(); + } + } + return plexPlayers; + } } diff --git a/src/main/java/dev/plex/module/ModuleManager.java b/src/main/java/dev/plex/module/ModuleManager.java index 793751c..133439d 100644 --- a/src/main/java/dev/plex/module/ModuleManager.java +++ b/src/main/java/dev/plex/module/ModuleManager.java @@ -3,6 +3,8 @@ package dev.plex.module; import com.google.common.collect.Lists; import dev.plex.Plex; import dev.plex.module.exception.ModuleLoadException; +//import dev.plex.module.loader.CustomClassLoader; +import dev.plex.module.loader.LibraryLoader; import dev.plex.util.PlexLog; import java.io.File; import java.io.IOException; @@ -23,6 +25,12 @@ public class ModuleManager { private final List modules = Lists.newArrayList(); + private final LibraryLoader libraryLoader; + + public ModuleManager() + { + this.libraryLoader = new LibraryLoader(Plex.get().getLogger()); + } public void loadAllModules() { @@ -56,8 +64,10 @@ public class ModuleManager String description = internalModuleConfig.getString("description", "A Plex module"); String version = internalModuleConfig.getString("version", "1.0"); + List libraries = internalModuleConfig.getStringList("libraries"); PlexModuleFile plexModuleFile = new PlexModuleFile(name, main, description, version); + plexModuleFile.setLibraries(libraries); Class module = (Class)Class.forName(main, true, loader); PlexModule plexModule = module.getConstructor().newInstance(); @@ -87,6 +97,7 @@ public class ModuleManager { PlexLog.log("Loading module " + module.getPlexModuleFile().getName() + " with version " + module.getPlexModuleFile().getVersion()); module.load(); +// this.libraryLoader.createLoader(module, module.getPlexModuleFile()); }); } diff --git a/src/main/java/dev/plex/module/PlexModule.java b/src/main/java/dev/plex/module/PlexModule.java index 61c4d78..95f2a2b 100644 --- a/src/main/java/dev/plex/module/PlexModule.java +++ b/src/main/java/dev/plex/module/PlexModule.java @@ -7,6 +7,8 @@ import dev.plex.listener.PlexListener; import java.io.File; import java.util.List; import java.util.Locale; + +//import dev.plex.module.loader.CustomClassLoader; import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/dev/plex/module/PlexModuleFile.java b/src/main/java/dev/plex/module/PlexModuleFile.java index 3d4ba39..699d696 100644 --- a/src/main/java/dev/plex/module/PlexModuleFile.java +++ b/src/main/java/dev/plex/module/PlexModuleFile.java @@ -1,7 +1,10 @@ package dev.plex.module; +import com.google.common.collect.ImmutableList; import lombok.Data; +import java.util.List; + @Data public class PlexModuleFile { @@ -9,4 +12,7 @@ public class PlexModuleFile private final String main; private final String description; private final String version; + + //TODO: does not work + private List libraries = ImmutableList.of(); } diff --git a/src/main/java/dev/plex/module/loader/CustomClassLoader.java b/src/main/java/dev/plex/module/loader/CustomClassLoader.java new file mode 100644 index 0000000..1fc2029 --- /dev/null +++ b/src/main/java/dev/plex/module/loader/CustomClassLoader.java @@ -0,0 +1,58 @@ +package dev.plex.module.loader; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; + +public class CustomClassLoader extends URLClassLoader { + /*public CustomClassLoader(URL[] urls, ClassLoader parent) { + super(urls, parent); + for (URL url : urls) { + super.addURL(url); + } + }*/ + + public CustomClassLoader(URL jarInJar, ClassLoader parent) { + super(new URL[]{extractJar(jarInJar)}, parent); + addURL(jarInJar); + } + + + + static URL extractJar(URL jarInJar) throws RuntimeException { + // get the jar-in-jar resource + if (jarInJar == null) { + throw new RuntimeException("Could not locate jar-in-jar"); + } + + // create a temporary file + // on posix systems by default this is only read/writable by the process owner + Path path; + try { + path = Files.createTempFile("plex-jarinjar", ".jar.tmp"); + } catch (IOException e) { + throw new RuntimeException("Unable to create a temporary file", e); + } + + // mark that the file should be deleted on exit + path.toFile().deleteOnExit(); + + // copy the jar-in-jar to the temporary file path + try (InputStream in = jarInJar.openStream()) { + Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + throw new RuntimeException("Unable to copy jar-in-jar to temporary path", e); + } + + try { + return path.toUri().toURL(); + } catch (MalformedURLException e) { + throw new RuntimeException("Unable to get URL from path", e); + } + } +} diff --git a/src/main/java/dev/plex/module/loader/LibraryLoader.java b/src/main/java/dev/plex/module/loader/LibraryLoader.java new file mode 100644 index 0000000..3b78f61 --- /dev/null +++ b/src/main/java/dev/plex/module/loader/LibraryLoader.java @@ -0,0 +1,236 @@ +package dev.plex.module.loader; + +import com.google.common.collect.Lists; +import dev.plex.Plex; +import dev.plex.module.PlexModule; +import dev.plex.module.PlexModuleFile; +import org.apache.maven.repository.internal.MavenRepositorySystemUtils; +import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.collection.CollectRequest; +import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory; +import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.impl.DefaultServiceLocator; +import org.eclipse.aether.repository.LocalRepository; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.repository.RepositoryPolicy; +import org.eclipse.aether.resolution.ArtifactResult; +import org.eclipse.aether.resolution.DependencyRequest; +import org.eclipse.aether.resolution.DependencyResolutionException; +import org.eclipse.aether.resolution.DependencyResult; +import org.eclipse.aether.spi.connector.RepositoryConnectorFactory; +import org.eclipse.aether.spi.connector.transport.TransporterFactory; +import org.eclipse.aether.transfer.AbstractTransferListener; +import org.eclipse.aether.transfer.TransferCancelledException; +import org.eclipse.aether.transfer.TransferEvent; +import org.eclipse.aether.transport.http.HttpTransporterFactory; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.net.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.logging.Level; +import java.util.logging.Logger; + +//TODO: doesn't work + +public class LibraryLoader { + + private final Logger logger; + private final RepositorySystem repository; + private final DefaultRepositorySystemSession session; + private final List repositories; + + public LibraryLoader(@NotNull Logger logger) { + this.logger = logger; + + DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator(); + locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class); + locator.addService(TransporterFactory.class, HttpTransporterFactory.class); + + this.repository = locator.getService(RepositorySystem.class); + this.session = MavenRepositorySystemUtils.newSession(); + + session.setChecksumPolicy(RepositoryPolicy.CHECKSUM_POLICY_FAIL); + session.setLocalRepositoryManager(repository.newLocalRepositoryManager(session, new LocalRepository("libraries"))); + session.setTransferListener(new AbstractTransferListener() { + @Override + public void transferStarted(@NotNull TransferEvent event) throws TransferCancelledException { + logger.log(Level.INFO, "Downloading {0}", event.getResource().getRepositoryUrl() + event.getResource().getResourceName()); + } + }); + session.setReadOnly(); + + this.repositories = repository.newResolutionRepositories(session, Arrays.asList(new RemoteRepository.Builder("central", "default", "https://repo.maven.apache.org/maven2").build())); + } + + @Nullable + public ClassLoader createLoader(@NotNull PlexModule module, @NotNull PlexModuleFile moduleFile) { + if (moduleFile.getLibraries().isEmpty()) { + return null; + } + logger.log(Level.INFO, "Loading libraries for {0}", new Object[]{moduleFile.getName()}); + logger.log(Level.INFO, "[{0}] Loading {1} libraries... please wait", new Object[] + { + moduleFile.getName(), moduleFile.getLibraries().size() + }); + + List dependencies = new ArrayList<>(); + List> classes = Lists.newArrayList(); + List files = Lists.newArrayList(); + for (String library : moduleFile.getLibraries()) { + Artifact artifact = new DefaultArtifact(library); + Dependency dependency = new Dependency(artifact, null); + + dependencies.add(dependency); + } + + DependencyResult result; + try { + result = repository.resolveDependencies(session, new DependencyRequest(new CollectRequest((Dependency) null, dependencies, repositories), null)); + } catch (DependencyResolutionException ex) { + throw new RuntimeException("Error resolving libraries", ex); + } + + List jarFiles = new ArrayList<>(); + for (ArtifactResult artifact : result.getArtifactResults()) { + File file = artifact.getArtifact().getFile(); + files.add(file); + URL url; + try { + url = file.toURI().toURL(); + } catch (MalformedURLException ex) { + throw new AssertionError(ex); + } + + jarFiles.add(url); + logger.log(Level.INFO, "[{0}] Loaded library {1}", new Object[] + { + moduleFile.getName(), file + }); + } + + /*List jarFiles = Lists.newArrayList(); + List artifacts = Lists.newArrayList(); + + + List> classes = new ArrayList<>(); + + for (String library : moduleFile.getLibraries()) { + Artifact artifact = new DefaultArtifact(library); + ArtifactRequest request = new ArtifactRequest(); + request.setArtifact(artifact); + request.addRepository(this.repositories.get(0)); + try { + ArtifactResult result = this.repository.resolveArtifact(this.session, request); + artifact = result.getArtifact(); + jarFiles.add(artifact.getFile().toURI().toURL()); + logger.log(Level.INFO, "Loaded library {0} for {1}", new Object[]{ + artifact.getFile().toURI().toURL().toString(), + moduleFile.getName() + }); + artifacts.add(artifact); + } catch (ArtifactResolutionException | MalformedURLException e) { + e.printStackTrace(); + } + + }*/ + logger.log(Level.INFO, "Loaded {0} libraries for {1}", new Object[]{jarFiles.size(), moduleFile.getName()}); + +// jarFiles.forEach(jar -> new CustomClassLoader(jar, Plex.class.getClassLoader())); +// jarFiles.forEach(jar -> new CustomClassLoader(jar, Plex.class.getClassLoader())); + + /*URLClassLoader loader = new URLClassLoader(jarFiles.toArray(URL[]::new), Plex.class.getClassLoader()); + + dependencies.forEach(artifact -> { + ArrayList classNames; + try { + classNames = getClassNamesFromJar(new JarFile(artifact.getArtifact().getFile())); + for (String className : classNames) { + Class classToLoad = Class.forName(className, true, loader); + classes.add(classToLoad); + } + } catch (Exception e) { + e.printStackTrace(); + } + }); + + classes.forEach(clazz -> logger.log(Level.INFO, "Loading class {0}", new Object[]{clazz.getName()}));*/ + jarFiles.forEach(url -> { + JarURLConnection connection; + try { + URL url2 = new URL("jar:" + url.toString() + "!/"); + /* + connection = (JarURLConnection) url2.openConnection(); + logger.log(Level.INFO, "Jar File: " + connection.getJarFileURL().toString());*/ + } catch (IOException e) { + e.printStackTrace(); + } + }); + return new URLClassLoader(files.stream().map(File::toURI).map(uri -> { + try { + return uri.toURL(); + } catch (MalformedURLException e) { + e.printStackTrace(); + return null; + } + }).toList().toArray(URL[]::new)/*jarFiles.stream().map(url -> { + try { + return new URL("jar:" + url.toString() + "!/"); + } catch (MalformedURLException e) { + e.printStackTrace(); + return null; + } + }).toList().toArray(URL[]::new)*/, Plex.class.getClassLoader())/*new CustomClassLoader(jarFiles.toArray(URL[]::new), Plex.class.getClassLoader())*/; + } + + /*public List> loadDependency(List paths) throws Exception { + + List> classes = new ArrayList<>(); + + for (Path path : paths) { + + URL url = path.toUri().toURL(); + URLClassLoader child = new URLClassLoader(new URL[]{url}, this.getClass().getClassLoader()); + + ArrayList classNames = getClassNamesFromJar(path.toString()); + + for (String className : classNames) { + Class classToLoad = Class.forName(className, true, child); + classes.add(classToLoad); + } + } + + return classes; + }*/ + + + private ArrayList getClassNamesFromJar(JarFile file) throws Exception { + ArrayList classNames = new ArrayList<>(); + try { + //Iterate through the contents of the jar file + Enumeration entries = file.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + //Pick file that has the extension of .class + if ((entry.getName().endsWith(".class"))) { + String className = entry.getName().replaceAll("/", "\\."); + String myClass = className.substring(0, className.lastIndexOf('.')); + classNames.add(myClass); + } + } + } catch (Exception e) { + throw new Exception("Error while getting class names from jar", e); + } + return classNames; + } +}