package net.glowstone.util; import net.glowstone.GlowServer; import javax.net.ssl.HttpsURLConnection; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Objects; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.logging.Level; /** * Simple library manager which downloads external dependencies. */ public final class LibraryManager { /** * The Maven repository to download from. */ final String repository; /** * The directory to store downloads in. */ final File directory; private final ExecutorService downloaderService = Executors.newCachedThreadPool(); private static final LibraryClassLoader loader = new LibraryClassLoader(); public LibraryManager() { // todo: allow configuration of repository, libraries, and directory repository = "https://repo.glowstone.net/service/local/repositories/central/content/"; directory = new File("lib"); } public static void addToClasspath(String... paths) { try { for (String path : Objects.requireNonNull(paths)) { String trimmedPath; if (path != null && !(trimmedPath = path.trim()).isEmpty()) { loader.addURL(Paths.get(trimmedPath).toUri().toURL()); } } } catch (IllegalArgumentException | MalformedURLException e) { RuntimeException re = new RuntimeException(e); re.setStackTrace(e.getStackTrace()); throw re; } } public static ClassLoader getLibraryClassLoader() { return loader; } public static void addToLibraryPath(String... paths) { for (String path : Objects.requireNonNull(paths)) { loader.addLibPath(path); } } public void run() { if (!directory.isDirectory() && !directory.mkdirs()) { GlowServer.logger.log(Level.SEVERE, "Could not create libraries directory: " + directory); } downloaderService.execute(new LibraryDownloader("org.xerial", "sqlite-jdbc", "3.15.1", "")); downloaderService.execute(new LibraryDownloader("mysql", "mysql-connector-java", "5.1.39", "")); downloaderService.execute(new LibraryDownloader("org.slf4j", "slf4j-jdk14", "1.7.15", "")); downloaderService.shutdown(); try { downloaderService.awaitTermination(5, TimeUnit.SECONDS); } catch (InterruptedException e) { GlowServer.logger.log(Level.SEVERE, "Library Manager thread interrupted: ", e); } } private class LibraryDownloader implements Runnable { private final String group; private final String library; private final String version; private String checksum; LibraryDownloader(String group, String library, String version, String checksum) { this.group = group; this.library = library; this.version = version; this.checksum = checksum; } @Override public void run() { // check if we already have it File file = new File(directory, library + '-' + version + ".jar"); if (!file.exists() && checksum(file, checksum)) { // download it GlowServer.logger.info("Downloading " + library + ' ' + version + "..."); try { URL downloadUrl = new URL(repository + group.replace('.', '/') + '/' + library + '/' + version + '/' + library + '-' + version + ".jar"); HttpsURLConnection connection = (HttpsURLConnection) downloadUrl.openConnection(); connection.setRequestProperty("User-Agent", "Mozilla/5.0"); try (ReadableByteChannel input = Channels.newChannel(connection.getInputStream()); FileOutputStream output = new FileOutputStream(file)) { output.getChannel().transferFrom(input, 0, Long.MAX_VALUE); GlowServer.logger.info("Downloaded " + library + ' ' + version + '.'); } } catch (IOException e) { GlowServer.logger.log(Level.WARNING, "Failed to download: " + library + ' ' + version, e); return; } } addToClasspath(file.getAbsolutePath()); } public boolean checksum(File file, String checksum) { // TODO: actually check checksum return true; } } private static class LibraryClassLoader extends URLClassLoader { static { ClassLoader.registerAsParallelCapable(); } private final Set<Path> libPaths = new CopyOnWriteArraySet<>(); private LibraryClassLoader() { super(new URL[0]); } @Override protected void addURL(URL url) { super.addURL(url); } protected void addLibPath(String path) { libPaths.add(Paths.get(path).toAbsolutePath()); } @Override protected String findLibrary(String libname) { String nativeName = System.mapLibraryName(libname); return libPaths.stream().map(path -> path.resolve(nativeName)).filter(Files::exists).map(Path::toString).findFirst().orElse(super.findLibrary(libname)); } } }