com.hmiard.leaves.webserver.Server.LeavesServer.java Source code

Java tutorial

Introduction

Here is the source code for com.hmiard.leaves.webserver.Server.LeavesServer.java

Source

/*
 * Leaves Framework
 * Copyright (C) 2015  Hugo Miard
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

package com.hmiard.leaves.webserver.Server;

import com.hmiard.leaves.framework.IO.Files.RootWriter;
import com.hmiard.leaves.framework.IO.ResourceHolder;
import com.hmiard.leaves.framework.LeavesApplication;
import com.hmiard.leaves.framework.Objects.Application.Leaf;
import com.hmiard.leaves.framework.Objects.Application.LeavesConfiguration;
import com.hmiard.leaves.framework.Objects.Exceptions.BadUriException;
import com.hmiard.leaves.framework.Objects.Exceptions.MissingBudException;
import com.hmiard.leaves.framework.Objects.Exceptions.UriMissmatchException;
import com.hmiard.leaves.framework.Objects.ObjectModel;
import com.hmiard.leaves.framework.Utils.Html.HtmlGenerator;
import com.hmiard.leaves.framework.Utils.Persistence.HibernateUtils;
import com.hmiard.leaves.webserver.Http.Exceptions.ReservedRouteException;
import com.hmiard.leaves.webserver.Http.GenericSession;
import com.hmiard.leaves.webserver.Http.LeavesResponse;
import com.hmiard.leaves.webserver.Http.LeavesSession;
import com.hmiard.leaves.webserver.IO.TempFileManager;
import com.hmiard.leaves.webserver.Leaves;
import com.hmiard.leaves.webserver.Server.Routing.LRoute;
import com.hmiard.leaves.webserver.Server.Routing.Router;
import com.hmiard.leaves.webserver.Server.Threading.LeavesMainThread;
import com.hmiard.leaves.webserver.Server.Threading.LeavesThreadRunner;
import com.hmiard.leaves.webserver.Utils.FileUtils;
import com.hmiard.leaves.webserver.Utils.UriUtils;
import org.joda.time.DateTime;
import org.w3c.dom.Element;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.HashSet;

/**
 * Encapsulates the Leaves server and threading strategy.
 */
public abstract class LeavesServer {

    /**
     * HTTP Keep-alive timeout (in milliseconds).
     */
    public static final int SOCKET_READ_TIMEOUT = 5000;
    /**
     * Leaves session storage timeout, in seconds.
     */
    public static long SESSION_TIMEOUT = 10000;

    /**
     * Path of the directory that wraps resources.
     */
    protected final String resourcesDir;
    protected final String hostname;
    protected final int port;
    protected HashSet<Socket> connections = new HashSet<>();

    public LeavesConfiguration configuration;

    public ResourceHolder resourceHolder;
    public TempFileManager fileManager;
    public LeavesThreadRunner threadRunner;
    public ServerSocket serverSocket;
    public LeavesWebSocketServer webSocketServer = null;
    public Router router;
    public ObjectModel objectModel;

    private LeavesMainThread mainThread = null;

    private boolean redirect;
    private String redirectionUri;

    public LeavesServer(LeavesConfiguration configuration) {
        this(configuration, "");
    }

    public LeavesServer(LeavesConfiguration configuration, String hostname) {

        LeavesApplication.logo();

        this.objectModel = new ObjectModel();
        this.configuration = configuration;
        this.resourcesDir = configuration.RESOURCES_DIRECTORY.getPath();
        this.hostname = hostname;
        this.port = configuration.SERVER_PORT;
        this.resourceHolder = new ResourceHolder(this);
        if (this.resourceHolder.isBloated())
            this.stop();
        this.fileManager = new TempFileManager();
        this.threadRunner = new LeavesThreadRunner(this);
        this.router = new Router(this);
        this.resetRedirection();
    }

    /**
     * Core server logic.
     *
     * @param session LeavesSession
     * @return LeavesResponse
     */
    public LeavesResponse serve(LeavesSession session) {

        GenericSession.Method method = session.getMethod();
        HashMap<String, String> files = new HashMap<>();
        LeavesResponse response;

        if (method.equals(GenericSession.Method.GET) || method.equals(GenericSession.Method.POST)) {

            try {
                session.parseBody(files);
            } catch (LeavesResponse.ResponseException e) {

                Leaves.say("ERROR : " + e.getMessage());
                e.printStackTrace();
                return new LeavesResponse(e.getStatus(), LeavesResponse.CommonMimeTypes.PLAINTEXT, e.getMessage());

            } catch (IOException e) {

                Leaves.say("ERROR : " + e.getMessage());
                e.printStackTrace();
                return new LeavesResponse(LeavesResponse.Status.INTERNAL_ERROR,
                        LeavesResponse.CommonMimeTypes.PLAINTEXT, "SERVER INTERNAL ERROR : " + e.getMessage());
            }
        }

        try {

            response = this.router.resolve(session);
            session.printRequestResult(response);

        } catch (Exception e) {

            Leaves.say("ERROR : " + e.getMessage());

            if (e instanceof MissingBudException || e instanceof BadUriException
                    || e instanceof Router.RouteNotFoundException)
                response = new LeavesResponse(LeavesResponse.Status.NOT_FOUND, LeavesResponse.CommonMimeTypes.HTML,
                        get404Leaf(session.getUri()));
            else
                response = new LeavesResponse(LeavesResponse.Status.INTERNAL_ERROR,
                        LeavesResponse.CommonMimeTypes.HTML, getExceptionLeaf(session.getUri(), e));
        }
        if (redirect) {
            response.setAs301Redirection(redirectionUri);
            resetRedirection();
        }
        return response;
    }

    /**
     * Starting the server.
     * If the socket is already in use, this will throw and IOExeception.
     *
     * @throws IOException
     */
    protected void start() throws IOException {

        this.serverSocket = new ServerSocket();
        this.serverSocket
                .bind((hostname.equals("")) ? new InetSocketAddress(port) : new InetSocketAddress(hostname, port));

        this.mainThread = new LeavesMainThread(this);
        this.mainThread.start();

        if (configuration.WEBSOCKETS_ACTIVE) {
            this.webSocketServer = new LeavesWebSocketServer(configuration.WEBSOCKETS_PORT, this.objectModel);
            this.webSocketServer.start();
        }
    }

    /**
     * Stopping the server.
     */
    public void stop() {

        try {
            if (this.serverSocket != null && !this.serverSocket.isClosed())
                FileUtils.close(this.serverSocket);

            closeAllConnections();
            HibernateUtils.shutdown();

            if (this.mainThread != null)
                this.mainThread.join();

            if (this.webSocketServer != null)
                this.webSocketServer.stop();

        } catch (InterruptedException | IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Injecting routes and static resources in the current server,
     * from another one.
     *
     * @param newServer the server that will be copied.
     */
    protected void inject(LeavesServer newServer) {

        HashMap<String, LRoute> routes = newServer.router.allRoutes();

        try {
            for (String k : routes.keySet())
                this.router.registerRoutes(routes.get(k));

            for (String uri : newServer.resourceHolder.getResourcesUris()) {
                ByteArrayInputStream res = newServer.resourceHolder.getResource(uri);
                this.resourceHolder.pushResource(res, uri);
                // Closing the old stream, since we've created a new one.
                newServer.resourceHolder.close(uri);
            }

        } catch (ReservedRouteException | IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Preparing a redirection.
     *
     * @param to String
     */
    public final void redirect(String to) {

        this.redirect = true;
        this.redirectionUri = (to.startsWith("/")) ? to : "/" + to;
    }

    private void resetRedirection() {

        this.redirect = false;
        this.redirectionUri = "/";
    }

    /**
     * Utility debug.
     *
     * @param something String to debug.
     */
    public static void say(String something) {

        DateTime date = new DateTime();
        System.out.println(date.toString() + " > Leaves Server : " + something);
    }

    /**
     * Pushing a new connection to the connections set.
     *
     * @param connection Socket
     */
    public synchronized void pushConnection(Socket connection) {
        this.connections.add(connection);
    }

    /**
     * Closing and removing an active connection from the set.
     *
     * @param connection Socket
     */
    public synchronized void removeConnection(Socket connection) {
        if (!connection.isClosed())
            FileUtils.close(connection);
        this.connections.remove(connection);
    }

    public synchronized void closeAllConnections() {

        try {
            this.connections.forEach(this::removeConnection);
        } catch (ConcurrentModificationException ignored) {
        }

    }

    /**
     * Trying to find a leaf named "404" for 404 page customisation.
     * If no such leaf is found, a default page is returned.
     *
     * @param uri Session uri
     * @return String
     */
    public String get404Leaf(String uri) {

        try {
            String pre = UriUtils.getPrefixBranch(uri);
            Leaf leaf404 = (pre.isEmpty()) ? objectModel.findLeaf("lll404") : objectModel.findLeaf(pre + ".lll404");
            return leaf404.export();
        } catch (Exception e) {
            return RootWriter.convert(HtmlGenerator.generateDefault404Page("ERROR : 404, page not found."));
        }
    }

    /**
     * Trying to find a leaf named "exception" to display an exception.
     * If no such leaf is found, a default page is returned.
     *
     * @param uri Session uri
     * @param ex Exception
     * @return String
     */
    public String getExceptionLeaf(String uri, Exception ex) {

        try {
            Leaf exceptionLeaf = objectModel.findLeaf("exception");
            Element e = exceptionLeaf.getElementById("error_title");
            if (e != null)
                e.setTextContent("Whoops ! " + ex.getClass().getSimpleName());
            e = exceptionLeaf.getElementById("error_message");
            if (e != null)
                e.setTextContent(ex.getMessage());
            return exceptionLeaf.export();
        } catch (Exception e) {
            return RootWriter.convert(HtmlGenerator.generateDefaultExceptionPage(ex));
        }
    }

    public String getResourcesDir() {
        return resourcesDir;
    }

    public String getHostname() {
        return hostname;
    }

    public int getPort() {
        return port;
    }
}