ratpack.launch.LaunchConfigFactory.java Source code

Java tutorial

Introduction

Here is the source code for ratpack.launch.LaunchConfigFactory.java

Source

/*
 * Copyright 2013 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ratpack.launch;

import com.google.common.collect.Iterables;
import com.sun.nio.zipfs.ZipFileSystemProvider;
import ratpack.api.Nullable;
import ratpack.ssl.SSLContexts;
import ratpack.util.internal.TypeCoercingProperties;

import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.*;
import java.util.*;

import static ratpack.util.ExceptionUtils.uncheck;

/**
 * Static factory methods for creating {@link LaunchConfig} objects using various strategies. <p> Designed to be used to construct a launch config via a {@link Properties} instance. <p> Most methods
 * eventually delegate to {@link #createWithBaseDir(ClassLoader, java.nio.file.Path, java.util.Properties)}. <p> When things go wrong, a {@link LaunchException} will be thrown.
 */
public abstract class LaunchConfigFactory {

    /**
     * Value: {@value}.
     *
     * @see #createFromGlobalProperties(ClassLoader, java.util.Properties, java.util.Properties)
     */
    public static final String SYSPROP_PREFIX_PROPERTY = "ratpack.syspropPrefix";

    /**
     * Value: {@value}.
     *
     * @see #createFromGlobalProperties(ClassLoader, java.util.Properties, java.util.Properties)
     */
    public static final String SYSPROP_PREFIX_DEFAULT = "ratpack.";

    public static final String CONFIG_RESOURCE_PROPERTY = "configResource";
    public static final String CONFIG_RESOURCE_DEFAULT = "ratpack.properties";

    private LaunchConfigFactory() {
    }

    /**
     * Delegates to {@link #createFromGlobalProperties(ClassLoader, String, java.util.Properties, java.util.Properties)}, extracting the propertyPrefix? from the globalProperties?. <p> Determines the
     * propertyPrefix? value and delegates to {@link #createFromGlobalProperties(ClassLoader, String, java.util.Properties, java.util.Properties)}. The value is determined by: {@code
     * globalProperties.getProperty(SYSPROP_PREFIX_PROPERTY, SYSPROP_PREFIX_DEFAULT)}
     *
     * @param classLoader The classloader to use to find the properties file.
     * @param globalProperties The environmental (override) properties
     * @param defaultProperties The default properties to use when a property is omitted
     * @return A launch config
     */
    public static LaunchConfig createFromGlobalProperties(ClassLoader classLoader, Properties globalProperties,
            Properties defaultProperties) {
        String propertyPrefix = globalProperties.getProperty(SYSPROP_PREFIX_PROPERTY, SYSPROP_PREFIX_DEFAULT);
        return createFromGlobalProperties(classLoader, propertyPrefix, globalProperties, defaultProperties);
    }

    /**
     * Delegates to {@link #createFromProperties(ClassLoader, java.util.Properties, java.util.Properties)} after extracting the prefixed? properties from the global properties. <p> The properties of
     * {@code globalProperties} that begin with {@code propertyPrefix} are extracted into a new property set, with the property names having the prefix removed. This becomes the {@code
     * overrideProperties} for the delegation to {@link #createFromProperties(ClassLoader, java.util.Properties, java.util.Properties)}.
     *
     * @param classLoader The classloader to use to find the properties file
     * @param propertyPrefix The prefix of properties in {@code globalProperties} that should be extracted to form the override properties
     * @param globalProperties The environmental (override) properties
     * @param defaultProperties The default properties to use when a property is omitted
     * @return A launch config
     */
    public static LaunchConfig createFromGlobalProperties(ClassLoader classLoader, String propertyPrefix,
            Properties globalProperties, Properties defaultProperties) {
        Properties deprefixed = new Properties();
        extractProperties(propertyPrefix, globalProperties, deprefixed);
        return createFromProperties(classLoader, deprefixed, defaultProperties);
    }

    /**
     * Extracts the properties prefixed with {@value #SYSPROP_PREFIX_DEFAULT} from the system properties, without the prefix.
     *
     * @return the properties prefixed with {@value #SYSPROP_PREFIX_DEFAULT} from the system properties, without the prefix.
     */
    public static Properties getDefaultPrefixedProperties() {
        Properties deprefixed = new Properties();
        extractProperties(SYSPROP_PREFIX_DEFAULT, System.getProperties(), deprefixed);
        return deprefixed;
    }

    private static void extractProperties(String propertyPrefix, Properties properties,
            Map<? super String, ? super String> destination) {
        for (String propertyName : properties.stringPropertyNames()) {
            if (propertyName.startsWith(propertyPrefix)) {
                destination.put(propertyName.substring(propertyPrefix.length()),
                        properties.getProperty(propertyName));
            }
        }
    }

    /**
     * Delegates to {@link #createFromFile(ClassLoader, java.nio.file.Path, java.nio.file.Path, java.util.Properties, java.util.Properties)}, after trying to find the config properties file. <p> The {@code
     * overrideProperties} are queried for the {@link #CONFIG_RESOURCE_PROPERTY} (defaulting to {@link #CONFIG_RESOURCE_DEFAULT}). The {@code classLoader} is asked for the classpath resource with this
     * name. <p> If the resource DOES NOT exist, {@link #createFromFile(ClassLoader, java.nio.file.Path, java.nio.file.Path, java.util.Properties, java.util.Properties)} is called with the current JVM user dir as
     * the {@code baseDir} and {@code null} as the {@code configFile}. <p> If the resource DOES exist, {@link #createFromFile(ClassLoader, java.nio.file.Path, java.nio.file.Path, java.util.Properties, java.util.Properties)} is called with that file resource as the {@code configFile}, and its parent directory as the {@code baseDir}.
     *
     * @param classLoader The classloader to use to find the properties file
     * @param overrideProperties The properties that provide the path to the config file resource, and any overrides
     * @param defaultProperties The default properties to use when a property is omitted
     * @return A launch config
     */
    public static LaunchConfig createFromProperties(ClassLoader classLoader, Properties overrideProperties,
            Properties defaultProperties) {
        String configResourceValue = overrideProperties.getProperty(CONFIG_RESOURCE_PROPERTY,
                CONFIG_RESOURCE_DEFAULT);
        URL configResourceUrl = classLoader.getResource(configResourceValue);

        Path configPath;
        Path baseDir;

        if (configResourceUrl == null) {
            configPath = Paths.get(configResourceValue);
            if (!configPath.isAbsolute()) {
                configPath = Paths.get(System.getProperty("user.dir"), configResourceValue);
            }

            baseDir = configPath.getParent();
        } else {
            configPath = resourceToPath(configResourceUrl);
            baseDir = configPath.getParent();
            if (baseDir == null && configPath.getFileSystem().provider() instanceof ZipFileSystemProvider) {
                baseDir = Iterables.getFirst(configPath.getFileSystem().getRootDirectories(), null);
            }

            if (baseDir == null) {
                throw new LaunchException("Cannot determine base dir given config resource: " + configPath);
            }
        }

        return createFromFile(classLoader, baseDir, configPath, overrideProperties, defaultProperties);
    }

    private static Path resourceToPath(URL resource) {
        URI uri = toUri(resource);

        String scheme = uri.getScheme();
        if (scheme.equals("file")) {
            return Paths.get(uri);
        }

        if (!scheme.equals("jar")) {
            throw new LaunchException("Cannot deal with class path resource url: " + uri);
        }

        String s = uri.toString();
        int separator = s.indexOf("!/");
        String entryName = s.substring(separator + 2);
        URI fileURI = URI.create(s.substring(0, separator));
        FileSystem fs;
        try {
            fs = FileSystems.newFileSystem(fileURI, Collections.<String, Object>emptyMap());
        } catch (IOException e) {
            throw uncheck(e);
        }
        return fs.getPath(entryName);
    }

    private static URI toUri(URL url) {
        try {
            return url.toURI();
        } catch (URISyntaxException e) {
            throw new LaunchException("Could not convert URL '" + url + "' to URI", e);
        }
    }

    /**
     * Delegates to {@link #createWithBaseDir(ClassLoader, java.nio.file.Path, java.util.Properties)}, after merging the properties from {@code configFile} and {@code overrideProperties}. <p> If {@code
     * configFile} exists and is not null, it is read as a properties file. It is then merged with {@code defaultProperties} & {@code overrideProperties}. The default properties are overridden by values
     * in the properties file, while the override properties will override these properties.
     *
     *
     * @param classLoader The classloader to use to find the properties file
     * @param baseDir The {@link LaunchConfig#getBaseDir()} of the eventual launch config
     * @param configFile The configuration properties file
     * @param overrideProperties The properties that override default and file properties
     * @param defaultProperties The default properties to use when a property is omitted
     * @return a launch config
     */
    public static LaunchConfig createFromFile(ClassLoader classLoader, Path baseDir, @Nullable Path configFile,
            Properties overrideProperties, Properties defaultProperties) {
        Properties fileProperties = new Properties(defaultProperties);
        if (configFile != null && Files.exists(configFile)) {
            try (InputStream inputStream = Files.newInputStream(configFile)) {
                fileProperties.load(inputStream);
            } catch (Exception e) {
                throw new LaunchException("Could not read config file '" + configFile + "'", e);
            }
        }

        fileProperties.putAll(overrideProperties);

        return createWithBaseDir(classLoader, baseDir, fileProperties);
    }

    /**
     * Constructs a launch config, based on the given properties. <p> See {@link Property} for details on the valid properties.
     *
     *
     * @param classLoader The classloader used to load the {@link ratpack.launch.LaunchConfigFactory.Property#HANDLER_FACTORY} class
     * @param baseDir The {@link LaunchConfig#getBaseDir()} value
     * @param properties The values to use to construct the launch config
     * @return A launch config
     */
    public static LaunchConfig createWithBaseDir(ClassLoader classLoader, Path baseDir, Properties properties) {
        TypeCoercingProperties props = new TypeCoercingProperties(properties, classLoader);
        try {
            Class<HandlerFactory> handlerFactoryClass;
            handlerFactoryClass = props.asClass(Property.HANDLER_FACTORY, HandlerFactory.class);
            if (handlerFactoryClass == null) {
                throw new LaunchException(
                        "No handler factory class specified (config property: " + Property.HANDLER_FACTORY + ")");
            }

            int port = props.asInt(Property.PORT, LaunchConfig.DEFAULT_PORT);
            InetAddress address = props.asInetAddress(Property.ADDRESS);
            URI publicAddress = props.asURI(Property.PUBLIC_ADDRESS);
            boolean reloadable = props.asBoolean(Property.RELOADABLE, false);
            int threads = props.asInt(Property.THREADS, 0);
            List<String> indexFiles = props.asList(Property.INDEX_FILES);
            InputStream sslKeystore = props.asStream(Property.SSL_KEYSTORE_FILE);
            String sslKeystorePassword = props.asString(Property.SSL_KEYSTORE_PASSWORD, "");
            int maxContentLength = props.asInt(Property.MAX_CONTENT_LENGTH,
                    LaunchConfig.DEFAULT_MAX_CONTENT_LENGTH);

            Map<String, String> otherProperties = new HashMap<>();
            extractProperties("other.", properties, otherProperties);

            HandlerFactory handlerFactory;
            try {
                handlerFactory = handlerFactoryClass.newInstance();
            } catch (Exception e) {
                throw new LaunchException("Could not instantiate handler factory: " + handlerFactoryClass.getName(),
                        e);
            }

            LaunchConfigBuilder launchConfigBuilder = LaunchConfigBuilder.baseDir(baseDir).port(port)
                    .address(address).publicAddress(publicAddress).reloadable(reloadable).threads(threads)
                    .maxContentLength(maxContentLength).indexFiles(indexFiles);

            if (sslKeystore != null) {
                try (InputStream stream = sslKeystore) {
                    launchConfigBuilder.ssl(SSLContexts.sslContext(stream, sslKeystorePassword));
                }
            }

            return launchConfigBuilder.other(otherProperties).build(handlerFactory);

        } catch (Exception e) {
            if (e instanceof LaunchException) {
                throw (LaunchException) e;
            } else {
                throw new LaunchException(
                        "Failed to create launch config with properties: " + properties.toString(), e);
            }
        }
    }

    /**
     * Constants for meaningful configuration properties.
     */
    public final class Property {
        private Property() {
        }

        /**
         * The port to listen for requests on. Defaults to {@link LaunchConfig#DEFAULT_PORT}. <p> <b>Value:</b> {@value}
         *
         * @see ratpack.launch.LaunchConfig#getPort()
         */
        public static final String PORT = "port";

        /**
         * The address to bind to. Defaults to {@code null} (all addresses). <p> If the value is not {@code null}, it will converted to an Inet Address via {@link java.net.InetAddress#getByName(String)}.
         * <p> <b>Value:</b> {@value} - (inet address)
         *
         * @see ratpack.launch.LaunchConfig#getAddress()
         */
        public static final String ADDRESS = "address";

        /**
         * Whether to reload the application if the script changes at runtime. Defaults to {@code false}. <p> <b>Value:</b> {@value} - (boolean)
         *
         * @see ratpack.launch.LaunchConfig#isReloadable()
         */
        public static final String RELOADABLE = "reloadable";

        /**
         * The full qualified classname of the handler factory (required). <p> This class MUST implement {@link HandlerFactory} and have a public no-arg constructor. <p> <b>Value:</b> {@value} - (string)
         *
         * @see ratpack.launch.LaunchConfig#getHandlerFactory()
         */
        public static final String HANDLER_FACTORY = "handlerFactory";

        /**
         * The number of worker threads to use. Defaults to 0. <p> <b>Value:</b> {@value} - (int)
         *
         * @see ratpack.launch.LaunchConfig#getThreads()
         */
        public static final String THREADS = "threads";

        /**
         * The public address of the site. <p> If the value is not {@code null}, it will converted to an URL. <p> <b>Value:</b> {@value} - (url)
         *
         * @see ratpack.launch.LaunchConfig#getPublicAddress()
         */
        public static final String PUBLIC_ADDRESS = "publicAddress";

        /**
         * The comma separated list of file names of files that can be served in place of a directory. <p> If the value is not {@code null}, it will be converted to a string list by splitting on ",". <p>
         * <b>Value:</b> {@value} - (comma separated string list)
         *
         * @see ratpack.launch.LaunchConfig#getIndexFiles()
         */
        public static final String INDEX_FILES = "indexFiles";

        /**
         * The absolute file path, URI or classpath location of the SSL keystore file.
         *
         * @see ratpack.launch.LaunchConfig#getSSLContext()
         */
        public static final String SSL_KEYSTORE_FILE = "ssl.keystore.file";

        /**
         * The password for the SSL keystore file.
         *
         * @see ratpack.launch.LaunchConfig#getSSLContext()
         */
        public static final String SSL_KEYSTORE_PASSWORD = "ssl.keystore.password";

        /**
         * The max conent lenght.
         *
         * @see ratpack.launch.LaunchConfig#getMaxContentLength()
         */
        public static final String MAX_CONTENT_LENGTH = "maxContentLength";
    }
}