blob: 695c6e7e82d877d94b1e41dd39d16050410e0e72 [file] [log] [blame]
/*
* Copyright 2013-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.facebook.buck.httpserver;
import com.facebook.buck.event.BuckEvent;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.DependencyGraph;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Maps;
import com.google.common.base.Throwables;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.UpgradeRequest;
import org.eclipse.jetty.websocket.api.UpgradeResponse;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
import java.io.IOException;
import java.util.Collections;
import java.util.Set;
@SuppressWarnings("serial")
public class StreamingWebSocketServlet extends WebSocketServlet {
private static final JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance;
// This is threadsafe
private final Set<MyWebSocket> connections;
public StreamingWebSocketServlet() {
this.connections = Collections.newSetFromMap(Maps.<MyWebSocket, Boolean>newConcurrentMap());
}
@Override
public void configure(WebSocketServletFactory factory) {
// Most implementations of this method simply invoke factory.register(DispatchSocket.class);
// however, that requires DispatchSocket to have a no-arg constructor. That does not work for
// us because we would like all WebSockets created by this factory to have a reference to this
// parent class. This is why we override the default WebSocketCreator for the factory.
WebSocketCreator wrapperCreator = new WebSocketCreator() {
@Override
public Object createWebSocket(UpgradeRequest req, UpgradeResponse resp) {
return new MyWebSocket();
}
};
factory.setCreator(wrapperCreator);
}
public void tellClients(BuckEvent event) {
try {
String message = new ObjectMapper().writeValueAsString(event);
tellAll(message);
} catch (IOException e) {
Throwables.propagate(e);
}
}
/** Sends the message to all WebSockets that are currently connected. */
private void tellAll(String message) {
for (MyWebSocket webSocket : connections) {
if (webSocket.isConnected()) {
webSocket.getRemote().sendStringByFuture(message);
}
}
}
/** This is the httpserver component of a WebSocket that maintains a session with one client. */
public class MyWebSocket extends WebSocketAdapter {
@Override
public void onWebSocketConnect(Session session) {
super.onWebSocketConnect(session);
connections.add(this);
// TODO(mbolin): Record all of the events for the last build that was started. For a fresh
// connection, replay all of the events to get the client caught up. Though must be careful,
// as this may not be a *new* connection from the client, but a *reconnection*, in which
// case we have to be careful about redrawing.
}
@Override
public void onWebSocketClose(int statusCode, String reason) {
super.onWebSocketClose(statusCode, reason);
connections.remove(this);
}
@Override
public void onWebSocketText(String message) {
super.onWebSocketText(message);
if (isNotConnected()) {
return;
}
// TODO(mbolin): Handle requests from client instead of only pushing data down.
}
}
static ObjectNode createJsonForGraph(DependencyGraph graph) {
ObjectNode nodesToDeps = jsonNodeFactory.objectNode();
for (BuildRule source : graph.getNodes()) {
ArrayNode deps = jsonNodeFactory.arrayNode();
for (BuildRule sink : graph.getOutgoingNodesFor(source)) {
deps.add(sink.getFullyQualifiedName());
}
nodesToDeps.put(source.getFullyQualifiedName(), deps);
}
return nodesToDeps;
}
}