Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introducing Java 9 support! (Fixes #576) #649

Merged
merged 1 commit into from
Dec 29, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@
<artifactId>jocl-main</artifactId>
<version>2.3.2</version>
</dependency>
<!-- JCL: Jar Class Loader -->
<dependency>
<groupId>org.xeustechnologies</groupId>
<artifactId>jcl-core</artifactId>
<version>2.8</version>
</dependency>
<!-- Tests -->
<dependency>
<groupId>junit</groupId>
Expand Down
27 changes: 27 additions & 0 deletions src/main/java/net/glowstone/GlowBootstrap.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package net.glowstone;

import net.glowstone.util.LibraryManager;
import org.xeustechnologies.jcl.JarClassLoader;
import org.xeustechnologies.jcl.JclObjectFactory;

public class GlowBootstrap {
/**
* Creates a new server on TCP port 25565 and starts listening for connections.
*
* @param args The command-line arguments.
*/
public static void main(String... args) {
LibraryManager libraryManager = new LibraryManager();
JarClassLoader libraryClassLoader = libraryManager.run();

if (libraryClassLoader != null) {
JclObjectFactory objectFactory = JclObjectFactory.getInstance();
Object main = objectFactory.create(libraryClassLoader, "net.glowstone.GlowMain", (Object) args);
Thread mainThread = new Thread((Runnable) main);
mainThread.setContextClassLoader(libraryClassLoader);
mainThread.start();
}
}


}
31 changes: 31 additions & 0 deletions src/main/java/net/glowstone/GlowMain.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package net.glowstone;

import java.util.logging.Level;

public class GlowMain implements Runnable {
private final String[] args;

public GlowMain(String[] args) {
this.args = args;
}

@Override
public void run() {
try {
GlowServer server = GlowServer.createFromArguments(args);

// we don't want to run a server when called with --version
if (server == null) {
return;
}

server.run();
} catch (SecurityException e) {
GlowServer.logger.log(Level.WARNING, "Error loading classpath!", e);
} catch (Throwable t) {
// general server startup crash
GlowServer.logger.log(Level.SEVERE, "Error during server startup.", t);
System.exit(1);
}
}
}
158 changes: 117 additions & 41 deletions src/main/java/net/glowstone/util/LibraryManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,38 @@
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.net.ssl.HttpsURLConnection;
import net.glowstone.GlowServer;
import org.xeustechnologies.jcl.JarClassLoader;

/**
* Simple library manager which downloads external dependencies.
*/
public final class LibraryManager {

/**
* True if we've detected a system ClassLoader that's not a URLClassLoader, so that we only log
* the error once.
* The logger for this class.
*/
private static volatile boolean nonUrlClassLoader = false;
public static final Logger logger = Logger.getLogger("LibraryManager");

/**
* The Maven repository to download from.
Expand All @@ -37,34 +46,83 @@ public final class LibraryManager {
*/
final File directory;

private final ExecutorService downloaderService = Executors.newCachedThreadPool();
private final ExecutorService executorService = Executors.newCachedThreadPool();
private final CompletionService<Library> downloaderService = new ExecutorCompletionService<>(executorService);

/**
* Creates a new instance of the library manager.
*/
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 void run() {
/**
* Downloads all runtime dependencies and adds them to a new JarClassLoader.
*/
public JarClassLoader run() {
if (!directory.isDirectory() && !directory.mkdirs()) {
GlowServer.logger.log(Level.SEVERE, "Could not create libraries directory: " + directory);
logger.log(Level.SEVERE, "Could not create libraries directory: " + directory);
}

Set<Future<Library>> futures = new HashSet<>();

futures.add(downloaderService.submit(new LibraryDownloader("org.xerial", "sqlite-jdbc", "3.21.0", "")));
futures.add(downloaderService.submit(new LibraryDownloader("mysql", "mysql-connector-java", "5.1.44", "")));
futures.add(downloaderService.submit(new LibraryDownloader("org.apache.logging.log4j", "log4j-api", "2.8.1", "")));
futures.add(downloaderService.submit(new LibraryDownloader("org.apache.logging.log4j", "log4j-core", "2.8.1", "")));

JarClassLoader libraryClassLoader = new JarClassLoader(LibraryManager.class.getClassLoader());
List<Library> failedLibraries = new ArrayList<>();

while (!futures.isEmpty()) {
Library library;
try {
Future<Library> libraryFuture = downloaderService.take();
futures.remove(libraryFuture);
library = libraryFuture.get();
} catch (InterruptedException | ExecutionException e) {
logger.log(Level.SEVERE, "Library Manager thread received an exception while waiting for futures to finish: ", e);
for (Future<Library> future : futures) {
future.cancel(true);
}
return null;
}

if (library.isSuccessfullyDownloaded()) {
try {
libraryClassLoader.add(library.getUri().toURL());
} catch (MalformedURLException e) {
logger.log(Level.SEVERE, "Failed to get URL for library: " + library.getName() + " " + library.getVersion(), e);
failedLibraries.add(library);
}
} else {
failedLibraries.add(library);
}
}

downloaderService.execute(new LibraryDownloader("org.xerial", "sqlite-jdbc", "3.21.0", ""));
downloaderService.execute(new LibraryDownloader("mysql", "mysql-connector-java", "5.1.44", ""));
downloaderService.execute(new LibraryDownloader("org.apache.logging.log4j", "log4j-api", "2.8.1", ""));
downloaderService.execute(new LibraryDownloader("org.apache.logging.log4j", "log4j-core", "2.8.1", ""));
downloaderService.shutdown();
executorService.shutdown();
try {
if (!downloaderService.awaitTermination(1, TimeUnit.MINUTES)) {
downloaderService.shutdownNow();
if (!executorService.awaitTermination(1, TimeUnit.MINUTES)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
GlowServer.logger.log(Level.SEVERE, "Library Manager thread interrupted: ", e);
logger.log(Level.SEVERE, "Library Manager thread interrupted: ", e);
return null;
}

if (!failedLibraries.isEmpty()) {
logger.log(Level.SEVERE, "Library Manager failed to download: " + failedLibraries.stream()
.map(Object::toString)
.collect(Collectors.joining("\", \"", "[\"", "\"]")));
return null;
}

return libraryClassLoader;
}

private class LibraryDownloader implements Runnable {
private class LibraryDownloader implements Callable<Library> {

private final String group;
private final String library;
Expand All @@ -79,46 +137,29 @@ private class LibraryDownloader implements Runnable {
}

@Override
public void run() {
if (nonUrlClassLoader) {
return;
}
ClassLoader sysLoader = ClassLoader.getSystemClassLoader();
if (!(sysLoader instanceof URLClassLoader)) {
nonUrlClassLoader = true;
GlowServer.logger.severe("This JVM isn't fully supported. Please use Oracle or"
+ " OpenJDK 8, not 9. (System class loader must be a URLClassLoader.)");
return;
}
public Library call() {
// check if we already have it
File file = new File(directory, getLibrary());
if (!checksum(file, checksum)) {
// download it
GlowServer.logger.info("Downloading " + library + ' ' + version + "...");
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 + '.');
logger.info("Downloaded " + library + ' ' + version + '.');
}
} catch (IOException e) {
GlowServer.logger.log(Level.WARNING, "Failed to download: " + library + ' ' + version, e);
logger.log(Level.WARNING, "Failed to download: " + library + ' ' + version, e);
file.delete();
return;
return new Library(false, library, version, file.toURI());
}
}

// hack it onto the classpath
try {
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
method.invoke(sysLoader, file.toURI().toURL());
} catch (ReflectiveOperationException | MalformedURLException e) {
GlowServer.logger.log(Level.WARNING, "Failed to add to classpath: " + library + " " + version, e);
}
return new Library(true, library, version, file.toURI());
}

String getLibrary() {
Expand All @@ -130,4 +171,39 @@ boolean checksum(File file, String checksum) {
return file.exists();
}
}

private static class Library {
private final boolean successfullyDownloaded;
private final String name;
private final String version;
private final URI uri;

private Library(boolean successfullyDownloaded, String name, String version, URI uri) {
this.successfullyDownloaded = successfullyDownloaded;
this.name = name;
this.version = version;
this.uri = uri;
}

public boolean isSuccessfullyDownloaded() {
return successfullyDownloaded;
}

public String getName() {
return name;
}

public String getVersion() {
return version;
}

public URI getUri() {
return uri;
}

@Override
public String toString() {
return name + " " + version;
}
}
}