package org.robotninjas.barge.jaxrs.ws;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.common.io.CharStreams;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.glassfish.jersey.servlet.ServletContainer;
import org.robotninjas.barge.jaxrs.RaftApplication;
import org.robotninjas.barge.jaxrs.RaftServer;
import org.robotninjas.barge.state.RaftProtocolListener;
import org.robotninjas.barge.state.StateTransitionListener;
import org.slf4j.bridge.SLF4JBridgeHandler;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
/**
* An instance for a Raft server using Jetty's embedded HTTP server.
* <p>
* This server exposes events of the lifecycle of Raft instance through a WebSocket API: Clients can connect to the
* <tt>/events/</tt> URI and get notified in real-time of logged events.
* </p>
*/
public class RaftJettyServer implements RaftServer<RaftJettyServer> {
private static final String help = "Usage: java -jar barge-http.jar [options] <server index>\n" +
"Options:\n" +
" -h : Displays this help message\n" +
" -c <configuration file> : Use given configuration file for cluster configuration\n" +
" This file is a simple property file with indices as keys and URIs as values, eg. like\n\n" +
" 0=http://localhost:1234\n" +
" 1=http://localhost:3456\n" +
" 2=http://localhost:4567\n\n" +
" Default is './barge.conf'\n" +
"<server index> : Index of this server in the cluster configuration\n";
private final Server server;
private final RaftApplication raftApplication;
private final WsEventListener events;
public static void main(String[] args) throws IOException, URISyntaxException {
muteJul();
File clusterConfiguration = new File("barge.conf");
int index = -1;
for (int i = 0; i < args.length; i++) {
switch (args[i]) {
case "-c":
clusterConfiguration = new File(args[++i]);
break;
case "-h":
usage();
System.exit(0);
default:
try {
index = Integer.parseInt(args[i].trim());
} catch (NumberFormatException e) {
usage();
System.exit(1);
}
break;
}
}
if (index == -1) {
usage();
System.exit(1);
}
URI[] uris = readConfiguration(clusterConfiguration);
RaftJettyServer server = new RaftJettyServer(index, uris, new File("log" + index)).start(uris[index].getPort());
waitForInput();
server.stop();
System.out.println("Bye!");
System.exit(0);
}
private static void waitForInput() throws IOException {
//noinspection ResultOfMethodCallIgnored
System.in.read();
}
private static void muteJul() {
java.util.logging.Logger.getLogger("").setLevel(Level.ALL);
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
}
private static URI[] readConfiguration(File clusterConfiguration) throws IOException, URISyntaxException {
List<URI> uris = Lists.newArrayList();
int lineNumber = 1;
for (String line : CharStreams.readLines(new FileReader(clusterConfiguration))) {
String[] pair = line.split("=");
if (pair.length != 2)
throw new IOException("Invalid cluster configuration at line " + lineNumber);
uris.add(Integer.parseInt(pair[0].trim()), new URI(pair[1].trim()));
}
return uris.toArray(new URI[uris.size()]);
}
private static void usage() {
System.out.println(help);
}
public RaftJettyServer(int serverIndex, URI[] uris, File logDir) {
server = new Server();
events = new WsEventListener();
raftApplication = new RaftApplication(serverIndex, uris, logDir, Collections.<StateTransitionListener>singleton(events), Collections.<RaftProtocolListener>singleton(events));
}
public RaftJettyServer start(int port) {
ServerConnector connector = new ServerConnector(server);
connector.setPort(port);
server.addConnector(connector);
// Setup the basic application "context" for this application at "/"
// This is also known as the handler tree (in jetty speak)
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
server.setHandler(context);
// Add a websocket to a specific path spec
ServletHolder holderEvents = new ServletHolder("ws-events", new EventServlet(events));
context.addServlet(holderEvents, "/events/*");
// Add Raft REST services endpoints
context.addServlet(new ServletHolder(new ServletContainer(raftApplication.makeResourceConfig())), "/raft/*");
try {
events.start();
server.start();
return this;
} catch (Throwable t) {
throw Throwables.propagate(t);
}
}
public void stop() {
try {
raftApplication.stop();
events.stop();
server.stop();
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
public URI getPort() {
return server.getURI();
}
@Override
public void clean() {
try {
raftApplication.clean();
} catch (IOException e) {
throw Throwables.propagate(e);
}
}
}