Java tutorial
/* * Copyright (c) 2015, OpinionLab * * This file is part of The Wall of Awesome. * * The Wall of Awesome is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The Wall of Awesome is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with The Wall of Awesome. If not, see <http://www.gnu.org/licenses/>. */ package com.opinionlab.woa; import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerResponse; import io.vertx.ext.web.Router; import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.handler.StaticHandler; import io.vertx.ext.web.handler.sockjs.SockJSHandler; import io.vertx.ext.web.handler.sockjs.SockJSHandlerOptions; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.pcollections.HashTreePMap; import org.pcollections.PMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.stringtemplate.v4.ST; import javax.mail.MessagingException; import javax.mail.NoSuchProviderException; import java.io.IOException; import java.util.Date; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import static com.cedarsoftware.util.io.JsonWriter.objectToJson; import static io.vertx.core.buffer.Buffer.buffer; import static io.vertx.core.http.HttpMethod.GET; import static java.lang.String.format; import static java.lang.System.getenv; public class WallOfAwesome { public static final Logger LOGGER = LoggerFactory.getLogger(WallOfAwesome.class); public static final Executor EXECUTOR = Executors.newCachedThreadPool(); public static final String AWESOME_TEMPLATE = loadAwesomeTemplate(); public static final PMap<String, Object> NO_TYPES = HashTreePMap.singleton("TYPE", false); private static String loadAwesomeTemplate() { try { return IOUtils .toString(WallOfAwesome.class.getClassLoader().getResourceAsStream("awesome-template.txt")); } catch (IOException e) { LOGGER.error("Unable to load template", e); return "<comment>"; } } private static void setRoot(Router router, StaticHandler handler) { router.route("/*").handler(handler); } public static void startServer(final int portNumber) { final Vertx vertx = Vertx.vertx(); HttpServer httpServer = vertx.createHttpServer(); final Router router = Router.router(vertx); router.route(GET, "/hello").handler(context -> context.response().end("Hello World!")); router.route("/download").handler(makeDownloadRoute()); router.route("/events/*").handler(makeEventStream(vertx)); setupStaticContent(router); httpServer.requestHandler(router::accept).listen(portNumber); } private static SockJSHandler makeEventStream(Vertx vertx) { final SockJSHandlerOptions options = new SockJSHandlerOptions().setHeartbeatInterval(2000); final SockJSHandler sockJSHandler = SockJSHandler.create(vertx, options); sockJSHandler.socketHandler(socket -> { final AtomicInteger openCount = new AtomicInteger(); final AtomicBoolean running = new AtomicBoolean(true); LOGGER.info(format("[OPEN] Sockets: %d", openCount.incrementAndGet())); socket.endHandler(aVoid -> { running.set(false); LOGGER.info(format("[CLOSE] Sockets: %d", openCount.decrementAndGet())); }); socket.handler(buffer -> { String command = buffer.toString(); if ("purge".equals(command)) { EXECUTOR.execute(() -> { try { AwesomeImap.purge(s -> socket.write(buffer(objectToJson( HashTreePMap.empty().plus("deleted", true).plus("id", s.getId()), NO_TYPES)))); } catch (NoSuchProviderException e) { LOGGER.error("Could not purge messages", e); } }); } else { LOGGER.error(format("Unknown command: %s", command)); } }); try { final AtomicReference<Date> latestDate = new AtomicReference<>(new Date(0)); Consumer<Awesome> publishAwesome = awesome -> { socket.write(buffer(objectToJson(awesome, NO_TYPES))); final Date receivedDate = awesome.getReceivedDate(); if (latestDate.get().before(receivedDate)) { latestDate.set(receivedDate); } }; AwesomeImap.fetchAwesome().forEach(publishAwesome); EXECUTOR.execute(() -> { LOGGER.info("Polling started."); try { while (running.get()) { AwesomeImap.fetchAwesomeSince(latestDate.get()).forEach(publishAwesome); Thread.sleep(1000); } } catch (Throwable t) { running.set(false); socket.close(); LOGGER.error("Polling ended ABNORMALLY", t); } finally { LOGGER.info("Polling ended normally."); } }); } catch (MessagingException e) { LOGGER.error("Unable to fetch messages.", e); } }); return sockJSHandler; } private static Handler<RoutingContext> makeDownloadRoute() { return routingContext -> EXECUTOR.execute(() -> { try { final HttpServerResponse response = routingContext.response(); final AtomicBoolean first = new AtomicBoolean(true); response.putHeader("Content-Type", "text/plain"); response.putHeader("Content-Disposition", "inline;filename=awesome.txt"); response.setChunked(true); response.write("BEGIN AWESOME\n\n"); AwesomeImap.fetchAwesome().forEach(awesome -> { if (!first.get()) { response.write("\n\n---\n\n"); } else { first.set(false); } response.write(new ST(AWESOME_TEMPLATE).add("awesome", awesome).render()); }); response.write("\n\nEND AWESOME"); response.end(); } catch (Throwable t) { LOGGER.error("Unable to fetch messages.", t); } }); } private static void setupStaticContent(Router router) { String externalLocation = getenv("WOA_DEV_LOC"); if (StringUtils.isEmpty(externalLocation)) { LOGGER.info("Public content located on classpath."); setRoot(router, StaticHandler.create("public")); } else { LOGGER.info("Public content located on directory: {}", externalLocation); setRoot(router, StaticHandler.create(externalLocation)); } } public static void main(String[] args) { startServer(4444); } }