package oncue; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import oncue.common.events.AgentStartedEvent; import oncue.common.events.AgentStoppedEvent; import oncue.common.events.JobCleanupEvent; import oncue.common.events.JobEnqueuedEvent; import oncue.common.events.JobFailedEvent; import oncue.common.events.JobProgressEvent; import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.PropertyNamingStrategy; import org.codehaus.jackson.map.SerializationConfig; import org.codehaus.jackson.node.ObjectNode; import play.libs.F.Callback0; import play.libs.Json; import play.mvc.WebSocket; import scala.concurrent.duration.Duration; import akka.actor.Cancellable; import akka.actor.UntypedActor; import akka.event.EventStream; import akka.event.Logging; import akka.event.LoggingAdapter; public class EventMachine extends UntypedActor { private final LoggingAdapter log = Logging.getLogger(getContext().system(), this); private static List<WebSocket.Out<JsonNode>> clients = new ArrayList<>(); private final static ObjectMapper mapper = new ObjectMapper(); private final Cancellable pinger = getContext() .system() .scheduler() .schedule(Duration.create(500, TimeUnit.MILLISECONDS), Duration.create(30000, TimeUnit.MILLISECONDS), getSelf(), "PING", getContext().dispatcher()); static { mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz")); mapper.configure(SerializationConfig.Feature.WRITE_ENUMS_USING_TO_STRING, true); mapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES); } @Override public void preStart() { super.preStart(); EventStream eventStream = getContext().system().eventStream(); eventStream.subscribe(getSelf(), AgentStartedEvent.class); eventStream.subscribe(getSelf(), AgentStoppedEvent.class); eventStream.subscribe(getSelf(), JobEnqueuedEvent.class); eventStream.subscribe(getSelf(), JobProgressEvent.class); eventStream.subscribe(getSelf(), JobFailedEvent.class); eventStream.subscribe(getSelf(), JobCleanupEvent.class); log.info("EventMachine is listening for OnCue events."); } @Override public void postStop() { pinger.cancel(); } public static void addSocket(WebSocket.In<JsonNode> in, final WebSocket.Out<JsonNode> out) { clients.add(out); in.onClose(new Callback0() { @Override public void invoke() { clients.remove(out); } }); } @Override public void onReceive(Object message) { if ("PING".equals(message)) { log.debug("Pinging websocket clients..."); for (WebSocket.Out<JsonNode> client : clients) { client.write(Json.toJson("PING")); } } else if (message instanceof AgentStartedEvent) { AgentStartedEvent agentStarted = (AgentStartedEvent) message; for (WebSocket.Out<JsonNode> client : clients) { ObjectNode event = constructEvent("agent:started", "agent", agentStarted.getAgent()); client.write(event); } } else if (message instanceof AgentStoppedEvent) { AgentStoppedEvent agentStopped = (AgentStoppedEvent) message; for (WebSocket.Out<JsonNode> client : clients) { ObjectNode event = constructEvent("agent:stopped", "agent", agentStopped.getAgent()); client.write(event); } } else if (message instanceof JobEnqueuedEvent) { JobEnqueuedEvent jobEnqueued = (JobEnqueuedEvent) message; for (WebSocket.Out<JsonNode> client : clients) { ObjectNode event = constructEvent("job:enqueued", "job", jobEnqueued.getJob().clonePublicView()); client.write(event); } } else if (message instanceof JobProgressEvent) { JobProgressEvent jobProgress = (JobProgressEvent) message; for (WebSocket.Out<JsonNode> client : clients) { ObjectNode event = constructEvent("job:progressed", "job", jobProgress.getJob().clonePublicView()); client.write(event); } } else if (message instanceof JobFailedEvent) { JobFailedEvent jobFailed = (JobFailedEvent) message; for (WebSocket.Out<JsonNode> client : clients) { ObjectNode event = constructEvent("job:failed", "job", jobFailed.getJob().clonePublicView()); client.write(event); } } else if (message instanceof JobCleanupEvent) { for (WebSocket.Out<JsonNode> client : clients) { ObjectNode event = constructEvent("jobs:cleanup", "jobs", null); client.write(event); } } } /** * Construct an event * * @param eventKey is the composite event key, e.g. 'agent:started' * @param subject is the subject of the event, e.g. 'agent' * @param payload is the object to serialise * @return a JSON object node representing the event */ private ObjectNode constructEvent(String eventKey, String subject, Object payload) { ObjectNode eventNode = Json.newObject(); ObjectNode payloadNode = Json.newObject(); eventNode.put(eventKey, payloadNode); payloadNode.put(subject, mapper.valueToTree(payload)); return eventNode; } }