pt.davidafsilva.slacker.server.HttpServerConfiguration.java Source code

Java tutorial

Introduction

Here is the source code for pt.davidafsilva.slacker.server.HttpServerConfiguration.java

Source

package pt.davidafsilva.slacker.server;

/*
 * #%L
 * slacker-server
 * %%
 * Copyright (C) 2015 David Silva
 * %%
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * #L%
 */

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;

import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.JksOptions;

/**
 * <p>Initializes the http server options from the available configuration.
 * Note that environmental variables may overwrite the configuration specified via the regular
 * json file. These env. variables are prefixed with <strong>SLACKER_*</strong>.</p>
 *
 * The supported SLACKER environment variables are:
 * <table summary="SLACK Variables">
 * <tr>
 * <td><strong>Variable</strong></td>
 * <td><strong>Description</strong></td>
 * </tr>
 * <tr>
 * <td>SLACKER_HTTP_PORT</td>
 * <td>The port for underlying HTTP server</td>
 * </tr>
 * <tr>
 * <td>PORT</td>
 * <td>The port for underlying HTTP server - required for heroku deployments</td>
 * </tr>
 * <tr>
 * <td>SLACKER_USE_SSL</td>
 * <td>Enables HTTPS instead of HTTP for underlying server</td>
 * </tr>
 * <tr>
 * <td>SLACKER_KEYSTORE_FILE</td>
 * <td>The keystore file location for server private-public key pair (required when SSL is
 * enabled)</td>
 * </tr>
 * <tr>
 * <td>SLACKER_KEYSTORE_PASS</td>
 * <td>The password for the specified keystore</td>
 * </tr>
 * </table>
 *
 * @author david
 */
final class HttpServerConfiguration {

    // the default value for HTTP port
    static final int DEFAULT_HTTP_PORT = 8080;
    // the default value for the use SSL flag
    static final boolean DEFAULT_USE_SSL = false;
    // the idle timeout for the connection (in seconds)
    static final int IDLE_TIMEOUT = 60;

    // the cipher suites to enable for SSL
    static final Collection<String> CIPHER_SUITES = Collections.unmodifiableList(
            Arrays.asList("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
                    "TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
                    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA"));

    // invalid configuration message
    static final String MISSING_PROPERTIES = "some required properties are missing: %s";

    // private constructor
    private HttpServerConfiguration() {
        throw new UnsupportedOperationException("no no no");
    }

    /**
     * Sets up the http server configuration based on the available environment variables (SLACK_*)
     * and current configuration via the json configuration file.
     *
     * @param config the current configuration
     * @return the configured server options
     */
    static HttpServerOptions setup(final JsonObject config) {
        // evaluate the environment variables
        evaluateEnvironmentVariables(config);

        // create the http server options
        final HttpServerOptions options = new HttpServerOptions().setIdleTimeout(IDLE_TIMEOUT)
                .setPort(config.getInteger(ConfigurationVariable.HTTP_PORT.name(), DEFAULT_HTTP_PORT))
                .setSsl(config.getBoolean(ConfigurationVariable.USE_SSL.name(), DEFAULT_USE_SSL));

        // setup the required SSL parameters
        if (options.isSsl()) {
            // validate the configuration
            validateOptions(config, ConfigurationVariable.KEY_STORE_FILE, ConfigurationVariable.KEY_STORE_PASS);

            // add the enabled cipher suites
            CIPHER_SUITES.stream().forEach(options::addEnabledCipherSuite);

            // set the both keystore location and keystore password
            options.setKeyStoreOptions(
                    new JksOptions().setPath(config.getString(ConfigurationVariable.KEY_STORE_FILE.name()))
                            .setPassword(config.getString(ConfigurationVariable.KEY_STORE_PASS.name())));
        }

        return options;
    }

    /**
     * Validates the options for runtime and if there are missing options, fails the start of this
     * verticle.
     *
     * @param config   the current configuration
     * @param required the required properties
     * @throws IllegalStateException if the provided configuration is invalid
     */
    private static void validateOptions(final JsonObject config, final ConfigurationVariable... required) {
        final String[] missing = Arrays.stream(required).map(ConfigurationVariable::name)
                .filter(prop -> !config.containsKey(prop)).toArray(String[]::new);
        if (missing.length > 0) {
            throw new IllegalStateException(String.format(MISSING_PROPERTIES, Arrays.toString(missing)));
        }
    }

    /**
     * Reads and overwrites the necessary configuration based on the available environment variables
     *
     * @param config the current configuration
     */
    private static void evaluateEnvironmentVariables(final JsonObject config) {
        // iterate over each configuration variable and read the environment variables in order
        // to overwrite the necessary configurations
        Arrays.stream(ConfigurationVariable.values())
                .forEach(variable -> Optional.ofNullable(System.getenv(variable.environmentName()))
                        .ifPresent(p -> config.put(variable.name(), variable.transformValue(p))));

        // special support for Heroku deployment
        Optional.ofNullable(System.getenv("PORT")).map(ConfigurationVariable.HTTP_PORT::transformValue)
                .ifPresent(p -> config.put(ConfigurationVariable.HTTP_PORT.name(), p));
    }

    // the enumeration of the support configuration variables
    enum ConfigurationVariable {
        HTTP_PORT(Integer::valueOf), USE_SSL(Boolean::valueOf), KEY_STORE_FILE(Objects::toString), KEY_STORE_PASS(
                Objects::toString);

        // the value transformer
        private final Function<String, Object> transformer;

        /**
         * Creates the configuration variable with the specified value transformer
         *
         * @param transformer the value transformer
         */
        ConfigurationVariable(final Function<String, Object> transformer) {
            this.transformer = transformer;
        }

        /**
         * Returns the name of the environment variable that links to this configuration
         *
         * @return the environment variable name
         */
        String environmentName() {
            return "SLACKER_" + name();
        }

        /**
         * Transformer the read property value into a configuration-typed value
         *
         * @param propertyValue the read property value
         * @return the transformed property value
         */
        Object transformValue(final String propertyValue) {
            return transformer.apply(propertyValue);
        }
    }
}