package io.vivarium.server; import java.io.IOException; import java.sql.Timestamp; import java.util.Optional; import org.java_websocket.WebSocket; import org.java_websocket.handshake.ClientHandshake; import com.fasterxml.jackson.databind.ObjectMapper; import com.googlecode.gwtstreamer.client.Streamer; import io.vivarium.net.jobs.CreateWorldJob; import io.vivarium.net.jobs.SimulationJob; import io.vivarium.net.messages.CreateJobMessage; import io.vivarium.net.messages.Message; import io.vivarium.net.messages.RequestResourceMessage; import io.vivarium.net.messages.ResourceFormat; import io.vivarium.net.messages.SendResourceMessage; import io.vivarium.net.messages.WorkerPledgeMessage; import io.vivarium.persistence.CreateWorldJobModel; import io.vivarium.persistence.JobModel; import io.vivarium.persistence.JobStatus; import io.vivarium.persistence.PersistenceModule; import io.vivarium.persistence.ResourceModel; import io.vivarium.persistence.RunSimulationJobModel; import io.vivarium.persistence.WorkerModel; import io.vivarium.serialization.JSONConverter; import io.vivarium.serialization.VivariumObjectCollection; import io.vivarium.util.UUID; import io.vivarium.util.Version; import io.vivarium.util.concurrency.StartableStoppable; import io.vivarium.util.concurrency.VoidFunctionScheduler; public class MessageRouter implements StartableStoppable { private final PersistenceModule _persistenceModule; private final ClientConnectionManager _connectionManager; private final VoidFunctionScheduler _enforcerScheduler; private final ObjectMapper mapper = new ObjectMapper(); public MessageRouter(PersistenceModule persistenceModule, ClientConnectionManager connectionManager, VoidFunctionScheduler enforcerScheduler) { _persistenceModule = persistenceModule; _connectionManager = connectionManager; _enforcerScheduler = enforcerScheduler; } @Override public void start() { _connectionManager.start(); _enforcerScheduler.start(); } @Override public void stop() { _enforcerScheduler.stop(); _connectionManager.stop(); } public void onOpen(WebSocket conn, ClientHandshake handshake) { System.out.println("SERVER: Web Socket Connection Opened. " + conn + " ~ " + handshake); } public void onClose(WebSocket conn, int code, String reason, boolean remote) { System.out.println( "SERVER: Web Socket Connection closed. " + conn + " ~ " + code + " # " + reason + " & " + remote); } public void onMessage(WebSocket conn, String message) { try { Message untypedMessage = mapper.readValue(message, Message.class); if (untypedMessage instanceof WorkerPledgeMessage) { acceptPledge(conn, (WorkerPledgeMessage) untypedMessage); } else if (untypedMessage instanceof SendResourceMessage) { acceptResource(conn, (SendResourceMessage) untypedMessage); } else if (untypedMessage instanceof RequestResourceMessage) { handleRequestForResource(conn, (RequestResourceMessage) untypedMessage); } else if (untypedMessage instanceof CreateJobMessage) { System.out.println("CreateJobMessage: " + message); acceptJob(conn, (CreateJobMessage) untypedMessage); } else { System.err.println("SERVER: Unhandled message of type " + untypedMessage.getClass().getSimpleName()); } } catch (IOException e) { e.printStackTrace(); } System.out.println( "SERVER: Web Socket Message . " + conn + " ~ " + message.substring(0, Math.min(message.length(), 200))); } private synchronized void acceptPledge(WebSocket webSocket, WorkerPledgeMessage pledge) { WorkerModel worker = new WorkerModel(pledge.getWorkerID(), pledge.getThroughputs(), pledge.isActive(), new Timestamp(System.currentTimeMillis()), pledge.getFileFormatVersion(), pledge.getCodeVersion()); _persistenceModule.persist(worker); _connectionManager.registerWorker(pledge.getWorkerID(), webSocket); _enforcerScheduler.execute(); } private void acceptResource(WebSocket webSocket, SendResourceMessage sendResourceMessage) { String dataString = sendResourceMessage.getDataString(); String jsonString; if (sendResourceMessage.getResourceFormat() == ResourceFormat.JSON) { jsonString = sendResourceMessage.getDataString(); } else if (sendResourceMessage.getResourceFormat() == ResourceFormat.GWT_STREAM) { VivariumObjectCollection collection = (VivariumObjectCollection) Streamer.get().fromString(dataString); jsonString = JSONConverter.serializerToJSONString(collection); } else { throw new IllegalStateException("Unexpected resource format " + sendResourceMessage.getResourceFormat()); } ResourceModel resource = new ResourceModel(sendResourceMessage.getResourceID(), jsonString, Version.FILE_FORMAT_VERSION); _persistenceModule.persist(resource); } private void acceptJob(WebSocket conn, CreateJobMessage createJobMessage) { JobModel job; if (createJobMessage.getJob() instanceof SimulationJob) { SimulationJob simulationJob = (SimulationJob) createJobMessage.getJob(); job = new RunSimulationJobModel(simulationJob.getJobID(), JobStatus.BLOCKED, (short) 0, null, null, null, simulationJob.getEndTick(), simulationJob.getInputResources(), simulationJob.getOutputResources(), simulationJob.getDependencies()); } else if (createJobMessage.getJob() instanceof CreateWorldJob) { CreateWorldJob createWorldJob = (CreateWorldJob) createJobMessage.getJob(); job = new CreateWorldJobModel(createWorldJob.getJobID(), JobStatus.BLOCKED, (short) 0, null, null, null, createWorldJob.getInputResources(), createWorldJob.getOutputResources(), createWorldJob.getDependencies()); } else { throw new IllegalStateException( "Unexpected job type " + createJobMessage.getJob().getClass().getSimpleName()); } _persistenceModule.persist(job); _enforcerScheduler.execute(); } private void handleRequestForResource(WebSocket webSocket, RequestResourceMessage requestResourceMessage) throws IOException { UUID resourceID = requestResourceMessage.getResourceID(); Optional<ResourceModel> resource = _persistenceModule.fetch(resourceID, ResourceModel.class); if (resource.isPresent() && resource.get().jsonData.isPresent()) { ResourceFormat resourceFormat = requestResourceMessage.getResourceFormat(); String jsonString = resource.get().jsonData.get(); String dataString = null; if (resourceFormat == ResourceFormat.JSON) { dataString = jsonString; } else if (resourceFormat == ResourceFormat.GWT_STREAM) { VivariumObjectCollection collection = JSONConverter.jsonStringToSerializerCollection(jsonString); dataString = Streamer.get().toString(collection); } else { throw new IllegalStateException("Unexpected resource format " + resourceFormat); } SendResourceMessage response = new SendResourceMessage(requestResourceMessage.getResourceID(), dataString, resourceFormat); webSocket.send(mapper.writeValueAsString(response)); } } public void onError(WebSocket conn, Exception ex) { System.out.println("SERVER: Web Socket Error . " + conn + " ~ " + ex); ex.printStackTrace(); } }