/******************************************************************************* * Copyright (c) 2014 Bruno Medeiros and other Contributors. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Bruno Medeiros - initial API and implementation *******************************************************************************/ package dtool.genie; import static melnorme.utilbox.core.Assert.AssertNamespace.assertNotNull; import static melnorme.utilbox.core.CoreUtil.array; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.Socket; import melnorme.utilbox.core.CommonException; import melnorme.utilbox.misc.FileUtil; import melnorme.utilbox.misc.StringUtil; import com.google.gson.stream.JsonToken; import com.google.gson.stream.MalformedJsonException; import dtool.engine.DToolServer; import dtool.genie.cmdline.StartServerOperation; import dtool.util.JsonReaderExt; import dtool.util.JsonWriterExt; public class GenieServer extends AbstractSocketServer { protected final DToolServer dtoolServer; protected File sentinelFile; public static final String ENGINE_NAME = "D Tool Genie"; public static final String ENGINE_VERSION = "0.1.0"; public static final String ENGINE_PROTOCOL_VERSION = "0.1"; public static File getSentinelFile() { return new File(System.getProperty("user.home"), StartServerOperation.SENTINEL_FILE_NAME); } public GenieServer(DToolServer dtoolServer, int portNumber) throws CommonException { super(portNumber); this.dtoolServer = assertNotNull(dtoolServer); dtoolServer.logMessage(" ------ Started Genie Server ------ "); logMessage("Listening on port: " + this.portNumber); this.sentinelFile = getSentinelFile(); try { FileUtil.writeStringToFile(sentinelFile, getServerPortNumber()+"", StringUtil.UTF8); } catch (IOException e) { throw new CommonException("Error writing to sentinel file " + sentinelFile, e); } } public DToolServer getDToolServer() { return dtoolServer; } @Override public void logMessage(String message) { dtoolServer.logMessage("# GenieServer: " + message); } @Override public void logError(String message, Throwable throwable) { dtoolServer.logError("# GenieServer: " + message, throwable); } @Override public void handleInternalError(Throwable throwable) { dtoolServer.handleInternalError(throwable); } @Override public void runServer() { try { super.runServer(); } finally { disposeSentinelFile(); } } public void shutdown() { closeServerSocket(); disposeSentinelFile(); // Note that some connection handlers can still be active } protected void disposeSentinelFile() { if(sentinelFile != null) { sentinelFile.delete(); sentinelFile = null; } } @Override protected GenieConnectionHandler createConnectionHandlerRunnable(Socket clientSocket) { return new GenieConnectionHandler(clientSocket); } @Override protected Thread createHandlerThread(ConnectionHandlerRunnable genieConnectionHandler) { Thread thread = super.createHandlerThread(genieConnectionHandler); thread.setName("GenieConnectionHandler:" + genieConnectionHandler.clientSocket.getPort()); return thread; } public class GenieConnectionHandler extends ConnectionHandlerRunnable { public GenieConnectionHandler(Socket clientSocket) { super(clientSocket); } @Override protected void handleConnectionStream() throws IOException { try( BufferedReader serverInput = new BufferedReader( new InputStreamReader(clientSocket.getInputStream(), StringUtil.UTF8)); OutputStreamWriter serverResponse = new OutputStreamWriter(clientSocket.getOutputStream(), StringUtil.UTF8); JsonReaderExt jsonParser = new JsonReaderExt(serverInput); JsonWriterExt jsonWriter = new JsonWriterExt(serverResponse); ) { try { jsonParser.setLenient(true); jsonWriter.setLenient(true); while(!jsonParser.isEOF()) { processJsonMessage(jsonParser, jsonWriter); } } catch (MalformedJsonException jsonException) { logProtocolMessageError(jsonException); return; } } } } public void logProtocolMessageError(Throwable throwable) { logError("Protocol message error: ", throwable); } public JsonCommandHandler[] getCommandHandlers() { return array( new AboutCommandHandler(this), new ShudtownCommandHandler(this), new FindDefinitionCommandHandler(this) ); } protected void processJsonMessage(JsonReaderExt jsonParser, JsonWriterExt jsonWriter) throws IOException { jsonParser.consumeExpected(JsonToken.BEGIN_OBJECT); try { String commandName = jsonParser.consumeExpectedPropName(); for (JsonCommandHandler commandHandler : getCommandHandlers()) { if(commandHandler.canHandle(commandName)) { commandHandler.processCommand(jsonParser, jsonWriter); return; } } new JsonCommandHandler(commandName, this) { @Override protected void writeResponseJsonContents() throws IOException { String msg = "Unknown command: " + commandName; logProtocolMessageError(validationError(msg)); jsonWriter.writeProperty("error", msg); }; }.processCommand(jsonParser, jsonWriter); } finally { jsonParser.consumeExpected(JsonToken.END_OBJECT); } } @SuppressWarnings("serial") public static class GenieCommandException extends CommonException { public GenieCommandException(String message) { super(message, null); } } public static class AboutCommandHandler extends JsonCommandHandler { public AboutCommandHandler(GenieServer genieServer) { super("about", genieServer); } @Override protected void writeResponseJsonContents() throws IOException { jsonWriter.writeProperty("engine", ENGINE_NAME); jsonWriter.writeProperty("version", ENGINE_VERSION); jsonWriter.writeProperty("protocol_version", ENGINE_PROTOCOL_VERSION); } } public static class ShudtownCommandHandler extends JsonCommandHandler { public ShudtownCommandHandler(GenieServer genieServer) { super("shutdown", genieServer); } @Override protected void processCommandResponse() throws IOException, GenieCommandException { super.processCommandResponse(); genieServer.shutdown(); } @Override protected void writeResponseJsonContents() throws IOException { } } }