ratpack.server.internal.DefaultServerConfigBuilder.java Source code

Java tutorial

Introduction

Here is the source code for ratpack.server.internal.DefaultServerConfigBuilder.java

Source

/*
 * Copyright 2014 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.server.internal;

import com.google.common.base.CaseFormat;
import com.google.common.base.StandardSystemProperty;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.io.ByteSource;
import com.google.common.io.Resources;
import ratpack.file.FileSystemBinding;
import ratpack.file.internal.DefaultFileSystemBinding;
import ratpack.func.Action;
import ratpack.func.Predicate;
import ratpack.server.ServerConfig;
import ratpack.ssl.SSLContexts;
import ratpack.util.internal.Paths2;

import javax.net.ssl.SSLContext;
import java.io.*;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static ratpack.util.Exceptions.uncheck;

public class DefaultServerConfigBuilder implements ServerConfig.Builder {

    private ServerEnvironment serverEnvironment;
    private FileSystemBinding baseDir;

    private int port;
    private InetAddress address;
    private boolean development;
    private int threads = ServerConfig.DEFAULT_THREADS;
    private URI publicAddress;
    private SSLContext sslContext;
    private int maxContentLength = ServerConfig.DEFAULT_MAX_CONTENT_LENGTH;

    //Variables to support configuring SSL
    private InputStream sslKeystore;
    private String sslKeystorePassword = "";

    private DefaultServerConfigBuilder(ServerEnvironment serverEnvironment, Optional<Path> baseDir) {
        if (baseDir.isPresent()) {
            this.baseDir = new DefaultFileSystemBinding(baseDir.get());
        }
        this.serverEnvironment = serverEnvironment;
        this.port = serverEnvironment.getPort();
        this.development = serverEnvironment.isDevelopment();
        this.publicAddress = serverEnvironment.getPublicAddress();
    }

    public static ServerConfig.Builder noBaseDir(ServerEnvironment serverEnvironment) {
        return new DefaultServerConfigBuilder(serverEnvironment, Optional.empty());
    }

    public static ServerConfig.Builder baseDir(ServerEnvironment serverEnvironment, Path baseDir) {
        return new DefaultServerConfigBuilder(serverEnvironment, Optional.of(baseDir.toAbsolutePath().normalize()));
    }

    public static ServerConfig.Builder findBaseDirProps(ServerEnvironment serverEnvironment,
            String propertiesPath) {
        String workingDir = StandardSystemProperty.USER_DIR.value();
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        BaseDirFinder.Result result = BaseDirFinder.find(workingDir, classLoader, propertiesPath)
                .orElseThrow(() -> new IllegalStateException("Could not find properties file '" + propertiesPath
                        + "' in working dir '" + workingDir + "' or context class loader classpath"));
        return baseDir(serverEnvironment, result.getBaseDir()).props(result.getResource());
    }

    @Override
    public ServerConfig.Builder port(int port) {
        this.port = port;
        return this;
    }

    @Override
    public ServerConfig.Builder address(InetAddress address) {
        this.address = address;
        return this;
    }

    @Override
    public ServerConfig.Builder development(boolean development) {
        this.development = development;
        return this;
    }

    @Override
    public ServerConfig.Builder threads(int threads) {
        if (threads < 1) {
            throw new IllegalArgumentException("'threads' must be > 0");
        }
        this.threads = threads;
        return this;
    }

    @Override
    public ServerConfig.Builder publicAddress(URI publicAddress) {
        this.publicAddress = publicAddress;
        return this;
    }

    @Override
    public ServerConfig.Builder maxContentLength(int maxContentLength) {
        this.maxContentLength = maxContentLength;
        return this;
    }

    @Override
    public ServerConfig.Builder ssl(SSLContext sslContext) {
        this.sslContext = sslContext;
        return this;
    }

    @Override
    public ServerConfig build() {
        loadSSLIfConfigured();
        return new DefaultServerConfig(baseDir, port, address, development, threads, publicAddress, sslContext,
                maxContentLength);
    }

    @Override
    public ServerConfig.Builder env() {
        return env(DEFAULT_ENV_PREFIX);
    }

    @Override
    public ServerConfig.Builder env(String prefix) {
        Map<String, String> filteredEnvVars = serverEnvironment.getenv().entrySet().stream()
                .filter(entry -> entry.getKey().startsWith(prefix))
                .collect(Collectors.toMap(entry -> CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL,
                        entry.getKey().replace(prefix, "")), Map.Entry::getValue));
        return props(filteredEnvVars);
    }

    @Override
    public ServerConfig.Builder props(ByteSource byteSource) {
        Properties properties = new Properties();
        try (InputStream is = byteSource.openStream()) {
            properties.load(is);
        } catch (IOException e) {
            throw uncheck(e);
        }
        return props(properties);
    }

    @Override
    public ServerConfig.Builder props(String path) {
        return props(Paths.get(path));
    }

    @Override
    public ServerConfig.Builder props(Path path) {
        return props(Paths2.asByteSource(path));
    }

    @Override
    public ServerConfig.Builder props(Map<String, String> map) {
        Map<String, BuilderAction<?>> propertyCoercions = createPropertyCoercions();
        map.entrySet().forEach(entry -> {
            BuilderAction<?> mapping = propertyCoercions.get(entry.getKey());
            if (mapping != null) {
                try {
                    mapping.apply(entry.getValue());
                } catch (Exception e) {
                    throw uncheck(e);
                }
            }
        });
        return this;
    }

    @Override
    public ServerConfig.Builder props(Properties properties) {
        Map<String, String> map = Maps.newHashMapWithExpectedSize(properties.size());
        properties.entrySet().forEach(e -> map.put(e.getKey().toString(), e.getValue().toString()));
        return props(map);
    }

    @Override
    public ServerConfig.Builder props(URL url) {
        return props(Resources.asByteSource(url));
    }

    @Override
    public ServerConfig.Builder sysProps() {
        return sysProps(DEFAULT_PROP_PREFIX);
    }

    @Override
    public ServerConfig.Builder sysProps(String prefix) {
        Map<String, String> filteredProperties = filter(serverEnvironment.getProperties().entrySet(),
                entry -> entry.getKey().toString().startsWith(prefix))
                        .collect(Collectors.toMap(p -> p.getKey().toString().replace(prefix, ""),
                                p -> p.getValue().toString()));
        return props(filteredProperties);
    }

    private ServerConfig.Builder sslKeystore(InputStream is) {
        sslKeystore = is;
        return this;
    }

    private ServerConfig.Builder sslKeystorePassword(String password) {
        this.sslKeystorePassword = password;
        return this;
    }

    private void loadSSLIfConfigured() {
        if (sslKeystore != null) {
            try (InputStream stream = sslKeystore) {
                this.ssl(SSLContexts.sslContext(stream, sslKeystorePassword));
            } catch (IOException | GeneralSecurityException e) {
                throw uncheck(e);
            }
        }
    }

    private static <E> Stream<E> filter(Collection<E> collection, Predicate<E> predicate) {
        return collection.stream().filter(predicate.toPredicate());
    }

    private static class BuilderAction<T> {

        private final Function<String, T> converter;

        private final Action<T> action;

        public BuilderAction(Function<String, T> converter, Action<T> action) {
            this.converter = converter;
            this.action = action;
        }

        public void apply(String value) throws Exception {
            action.execute(converter.apply(value));
        }

    }

    /**
     * Gets a property value as an InputStream. The property value can be any of:
     * <ul>
     *   <li>An absolute file path to a file that exists.</li>
     *   <li>A valid URI.</li>
     *   <li>A classpath resource path loaded via the ClassLoader passed to the constructor.</li>
     * </ul>
     *
     * @param path the path to the resource
     * @return an InputStream or <code>null</code> if the property does not exist.
     */
    private static InputStream asStream(String path) {
        try {
            InputStream stream = null;
            if (path != null) {
                // try to treat it as a File path
                File file = new File(path);
                if (file.isFile()) {
                    stream = new FileInputStream(file);
                } else {
                    // try to treat it as a URL
                    try {
                        URL url = new URL(path);
                        stream = url.openStream();
                    } catch (MalformedURLException e) {
                        // try to treat it as a resource path
                        stream = DefaultServerConfigBuilder.class.getClassLoader().getResourceAsStream(path);
                        if (stream == null) {
                            throw new FileNotFoundException(path);
                        }
                    }
                }
            }
            return stream;
        } catch (IOException e) {
            throw uncheck(e);
        }
    }

    private static InetAddress inetAddress(String s) {
        return uncheck(() -> InetAddress.getByName(s));
    }

    private Map<String, BuilderAction<?>> createPropertyCoercions() {
        return ImmutableMap.<String, BuilderAction<?>>builder()
                .put("port", new BuilderAction<>(Integer::parseInt, DefaultServerConfigBuilder.this::port))
                .put("address",
                        new BuilderAction<>(DefaultServerConfigBuilder::inetAddress,
                                DefaultServerConfigBuilder.this::address))
                .put("development",
                        new BuilderAction<>(Boolean::parseBoolean, DefaultServerConfigBuilder.this::development))
                .put("threads", new BuilderAction<>(Integer::parseInt, DefaultServerConfigBuilder.this::threads))
                .put("publicAddress",
                        new BuilderAction<>(URI::create, DefaultServerConfigBuilder.this::publicAddress))
                .put("maxContentLength",
                        new BuilderAction<>(Integer::parseInt, DefaultServerConfigBuilder.this::maxContentLength))
                .put("sslKeystoreFile",
                        new BuilderAction<>(DefaultServerConfigBuilder::asStream,
                                DefaultServerConfigBuilder.this::sslKeystore))
                .put("sslKeystorePassword", new BuilderAction<>(Function.identity(),
                        DefaultServerConfigBuilder.this::sslKeystorePassword))
                .build();
    }
}