package org.cloudname.samples.service; import org.cloudname.core.BackendManager; import org.cloudname.flags.Flag; import org.cloudname.flags.Flags; import org.cloudname.service.CloudnameService; import org.cloudname.service.Endpoint; import org.cloudname.service.InstanceCoordinate; import org.cloudname.service.ServiceCoordinate; import org.cloudname.service.ServiceData; import org.cloudname.service.ServiceHandle; import org.cloudname.service.ServiceListener; import org.cloudname.testtools.Net; import spark.Spark; import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Logger; /** * A sample service. This service provides a simple web page with a coordinate, known peers and a * shutdown button. * * @author stalehd@gmail.com */ public class GhostService { private static final Logger LOG = Logger.getLogger(GhostService.class.getName()); @Flag (name = "cloudname-url", description = "Cloudname URL", required = true) private static String cloudnameUrl = null; @Flag(name = "service-name", description = "Service name", required = false) private static String myServiceName = null; @Flag (name = "coordinate", description = "Service coordinate", required = false) private static String myCoordinate = "blinky.test.local"; private final CountDownLatch terminateLatch = new CountDownLatch(1); private final AtomicReference<InstanceCoordinate> assignedCoordinate = new AtomicReference<>(); private final Map<String, InstanceCoordinate> peerMap = new ConcurrentHashMap<>(); private final CloudnameService service; private final int sparkPort; private GhostService() { service = new CloudnameService(BackendManager.getBackend(cloudnameUrl)); try { sparkPort = Net.getFreePort(); } catch (final IOException ioe) { throw new RuntimeException(ioe); } } private void connectToCloudname() { final ServiceData serviceData = new ServiceData(); for (final String endpoint : Net.getHostInterfaces()) { serviceData.addEndpoint(new Endpoint("http", endpoint, sparkPort)); } try (final ServiceHandle handle = service.registerService(getServiceCoordinate(), serviceData)) { assignedCoordinate.set(handle.getCoordinate()); service.addServiceListener(getServiceCoordinate(), new ServiceListener() { @Override public void onServiceCreated( final InstanceCoordinate coordinate, final ServiceData serviceData) { LOG.info("Adding " + coordinate + " to peer set"); peerMap.put(coordinate.toCanonicalString(), coordinate); } @Override public void onServiceDataChanged( final InstanceCoordinate coordinate, final ServiceData data) { } @Override public void onServiceRemoved(final InstanceCoordinate coordinate) { LOG.info("Removing " + coordinate + " from peer set"); peerMap.remove(coordinate.toCanonicalString()); } }); try { terminateLatch.await(); LOG.info("Stopping service"); Spark.stop(); } catch (final InterruptedException ie) { throw new RuntimeException(ie); } } LOG.info("Closed connection"); } private ServiceCoordinate getServiceCoordinate() { if (myServiceName != null) { return new ServiceCoordinate.Builder() .fromCoordinate(ServiceCoordinate.parse(myCoordinate)) .setService(myServiceName) .build(); } return ServiceCoordinate.parse(myCoordinate); } private String getPeerSetList() { final StringBuilder sb = new StringBuilder().append("<ul>"); for (final InstanceCoordinate coordinate : peerMap.values()) { sb.append("<li>").append(coordinate.toCanonicalString()).append("</li>"); } sb.append("</ul>"); return sb.toString(); } private String getStatusPage() { return "<html lang=\"en\">\n" + " <head>\n" + " <title>Hello there!</title>\n" + " <style>\n" + " body { \n" + " background-color: black; \n" + " font-family: sans-serif; \n" + " font-size: 14pt; \n" + " color: lightblue;\n" + " }\n" + " </style>\n" + " </head>\n" + " <body>\n" + " <p>My coordinate is <strong>" + assignedCoordinate.get().toCanonicalString() + "</strong></p>\n" + " <h1>My peers</h1>\n" + getPeerSetList() + " <hr/>\n" + " <form action=\"/shutdown\" method=\"POST\">\n" + " <button type=\"submit\">Shut down</button>\n" + " </form>\n" + " </body>\n" + "</html>"; } private void setupWebserver() { Spark.port(sparkPort); Spark.get("/", (req, res) -> getStatusPage()); Spark.post("/shutdown", (req, res) -> { terminateLatch.countDown(); return "<strong>Shutting down in a jiffy!</strong>" + "<script>setTimeout(function() { window.close(); }, 1000);</script>"; }); Spark.init(); } /** * Launch the test server. */ public static void main(final String[] args) { new Flags().loadOpts(GhostService.class).parse(args); final GhostService service = new GhostService(); Executors.newSingleThreadExecutor().execute(() -> { Spark.awaitInitialization(); service.connectToCloudname(); }); service.setupWebserver(); } }