com.opinionlab.woa.WallOfAwesome.java Source code

Java tutorial

Introduction

Here is the source code for com.opinionlab.woa.WallOfAwesome.java

Source

/*
 * 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);
    }
}