ProjectAres/API/minecraft/src/main/java/tc/oc/api/minecraft/MinecraftServiceImpl.java

179 lines
6.3 KiB
Java

package tc.oc.api.minecraft;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import com.google.common.base.Function;
import com.google.common.eventbus.EventBus;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import tc.oc.api.connectable.Connectable;
import tc.oc.api.docs.Server;
import tc.oc.api.docs.virtual.ServerDoc;
import tc.oc.api.exceptions.ApiNotConnected;
import tc.oc.api.message.MessageListener;
import tc.oc.api.message.MessageService;
import tc.oc.api.message.types.ModelUpdate;
import tc.oc.api.minecraft.config.MinecraftApiConfiguration;
import tc.oc.api.minecraft.servers.LocalServerDocument;
import tc.oc.api.minecraft.servers.LocalServerReconfigureEvent;
import tc.oc.api.minecraft.servers.StartupServerDocument;
import tc.oc.api.servers.ServerService;
import tc.oc.commons.core.logging.Loggers;
import tc.oc.commons.core.reflect.Methods;
import tc.oc.commons.core.util.MethodHandleInvoker;
import tc.oc.commons.core.util.ProxyUtils;
import tc.oc.minecraft.scheduler.SyncExecutor;
@Singleton
public class MinecraftServiceImpl implements MinecraftService, MessageListener, Connectable {
private final Logger logger;
private final EventBus eventBus;
private final SyncExecutor syncExecutor;
private final ServerService serverService;
private final MinecraftApiConfiguration apiConfiguration;
private final StartupServerDocument startupDocument;
private final MessageService serverQueue;
private final Server everfreshLocalServer;
private @Nullable Server server;
@Inject MinecraftServiceImpl(Loggers loggers,
EventBus eventBus,
SyncExecutor syncExecutor,
ServerService serverService,
MinecraftApiConfiguration apiConfiguration,
MessageService serverQueue,
LocalServerDocument localServerDocument,
StartupServerDocument startupDocument) {
this.logger = loggers.get(getClass());
this.eventBus = eventBus;
this.syncExecutor = syncExecutor;
this.serverService = serverService;
this.apiConfiguration = apiConfiguration;
this.serverQueue = serverQueue;
this.everfreshLocalServer = ProxyUtils.newProxy(Server.class, new MethodHandleInvoker() {
@Override
protected Object targetFor(Method method) {
if(server != null) return server;
if(Methods.respondsTo(localServerDocument, method)) return localServerDocument;
throw new ApiNotConnected();
}
});
this.startupDocument = startupDocument;
}
@Override
public boolean listenWhileSuspended() {
return true;
}
private void assertConnected() throws ApiNotConnected {
if(server == null) {
throw new ApiNotConnected();
}
}
@Override
public Server getLocalServer() {
assertConnected();
return server;
}
@Override
public boolean isLocalServer(ServerDoc.Identity server) {
return getLocalServer()._id().equals(server._id());
}
/**
* Return a magic {@link Server} document for the local server that
* always has the most recent data. If the API is not connected,
* the fields provided by {@link LocalServerDocument} will work,
* but trying to read any other field will throw {@link ApiNotConnected}.
*/
@Override
public Server everfreshLocalServer() {
return everfreshLocalServer;
}
/**
* Send a server configuration change to the remote API. The API will respond with
* the latest version of the server document, and only at that point will the local
* document be modified. This is done by calling {@link #handleReconfigure},
* and subclasses can override that method if they want to fire a reconfigure event.
*
* This method returns a future that completes after the API responds to the update
* AND the local server document has been replaced with the result.
*/
@Override
public ListenableFuture<Server> updateLocalServer(ServerDoc.Partial update) {
return Futures.transform(
serverService.update(apiConfiguration.serverId(), update),
(Function<? super Server, ? extends Server>) result -> {
handleLocalReconfigure(result);
return result;
},
syncExecutor
);
}
@HandleMessage
public void handleReconfigure(ModelUpdate<Server> message) {
if(server != null && server._id().equals(message.document()._id())) {
handleLocalReconfigure(message.document());
}
}
protected void handleLocalReconfigure(Server newConfig) {
Server oldConfig = this.server;
this.server = newConfig;
if(logger.isLoggable(Level.FINE)) {
logger.fine("Local server reconfigured: " + newConfig);
}
eventBus.post(new LocalServerReconfigureEvent(oldConfig, newConfig));
}
@Override
public void connect() throws IOException {
try {
serverQueue.subscribe(this, syncExecutor);
handleLocalReconfigure(serverService.update(apiConfiguration.serverId(), startupDocument).get());
logger.info("Connected to API as server." + getLocalServer()._id());
} catch (Exception e) {
this.processIntoIOException(e);
}
}
@Override
public void disconnect() throws IOException {
try {
serverService.update(
apiConfiguration.serverId(),
(ServerDoc.Online) () -> false
).get();
serverQueue.unsubscribe(this);
} catch (Exception e) {
processIntoIOException(e);
}
this.server = null;
}
private void processIntoIOException(Throwable t) throws IOException {
if (t instanceof IOException) throw (IOException)t;
if (t instanceof ExecutionException) this.processIntoIOException(t.getCause());
throw new IOException(t);
}
}