All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.deephaven.server.runner.MainHelper Maven / Gradle / Ivy

The newest version!
//
// Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending
//
package io.deephaven.server.runner;

import io.deephaven.base.system.PrintStreamGlobals;
import io.deephaven.configuration.CacheDir;
import io.deephaven.configuration.ConfigDir;
import io.deephaven.configuration.Configuration;
import io.deephaven.configuration.DataDir;
import io.deephaven.engine.table.impl.remote.ConstructSnapshot;
import io.deephaven.internal.log.Bootstrap;
import io.deephaven.internal.log.LoggerFactory;
import io.deephaven.io.logger.LogBufferGlobal;
import io.deephaven.io.logger.LogBufferInterceptor;
import io.deephaven.io.logger.Logger;
import io.deephaven.ssl.config.Identity;
import io.deephaven.ssl.config.IdentityPrivateKey;
import io.deephaven.ssl.config.SSLConfig;
import io.deephaven.ssl.config.SSLConfig.ClientAuth;
import io.deephaven.ssl.config.Trust;
import io.deephaven.ssl.config.TrustCertificates;
import io.deephaven.util.HeapDump;
import io.deephaven.util.annotations.VisibleForTesting;
import io.deephaven.util.process.ProcessEnvironment;
import org.jetbrains.annotations.NotNull;
import org.slf4j.bridge.SLF4JBridgeHandler;

import java.io.IOException;
import java.io.Reader;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;

public class MainHelper {

    public static final String SSL_IDENTITY_TYPE = "ssl.identity.type";
    public static final String SSL_IDENTITY_CERT_CHAIN_PATH = "ssl.identity.certChainPath";
    public static final String SSL_IDENTITY_PRIVATE_KEY_PATH = "ssl.identity.privateKeyPath";
    public static final String SSL_TRUST_TYPE = "ssl.trust.type";
    public static final String SSL_TRUST_PATH = "ssl.trust.path";
    public static final String SSL_CLIENT_AUTH = "ssl.clientAuthentication";
    public static final String PRIVATEKEY = "privatekey";
    public static final String CERTS = "certs";
    public static final String DEEPHAVEN_APPLICATION_PROPERTY = "deephaven.application";
    public static final String DEEPHAVEN_APPLICATION_ENV = "DEEPHAVEN_APPLICATION";

    private static void bootstrapSystemProperties(String[] args) throws IOException {
        if (args.length > 1) {
            throw new IllegalArgumentException("Expected 0 or 1 argument");
        }
        if (args.length == 1) {
            bootstrapFromFile(Path.of(args[0]));
            return;
        }
    }

    private static void bootstrapFromFile(Path configFile) throws IOException {
        Bootstrap.printf("# Bootstrapping from file '%s'%n", configFile);
        try (final Reader reader = Files.newBufferedReader(configFile, Charset.defaultCharset())) {
            System.getProperties().load(reader);
        }
    }

    @VisibleForTesting
    public static void bootstrapProjectDirectories() throws IOException {
        final String applicationName =
                applicationProperty().or(MainHelper::applicationEnvironmentVariable).orElse("deephaven");

        // Default directories based on the underlying OS conventions
        final dev.dirs.ProjectDirectories defaultDirectories =
                dev.dirs.ProjectDirectories.from("io", "Deephaven Data Labs", applicationName);

        final Path cacheDir = CacheDir.getOrSet(defaultDirectories.cacheDir);
        final Path configDir = ConfigDir.getOrSet(defaultDirectories.configDir);
        final Path dataDir = DataDir.getOrSet(defaultDirectories.dataDir);

        Files.createDirectories(cacheDir);
        Files.createDirectories(dataDir);

        Bootstrap.printf("%s=%s%n%s=%s%n%s=%s%n",
                CacheDir.PROPERTY,
                cacheDir,
                ConfigDir.PROPERTY,
                configDir,
                DataDir.PROPERTY,
                dataDir);
    }

    /**
     * Common init method to share between main() implementations.
     *
     * @param args the args
     * @param mainClass the main class
     * @return the current configuration instance to be used when configuring the rest of the server
     * @throws IOException if an I/O exception occurs
     */
    @NotNull
    public static Configuration init(String[] args, Class mainClass) throws IOException {
        Bootstrap.printf("# Starting %s%n", mainClass.getName());

        // No classes should be loaded before we bootstrap additional system properties
        bootstrapSystemProperties(args);
        bootstrapProjectDirectories();

        // Capture the original System.out and System.err early
        PrintStreamGlobals.init();

        // Safety checks
        SafetyChecks.check();

        // Since our dagger injection happens later, we need to provider a static way to get the LogBuffer (for example,
        // logback configuration may reference LogBufferAppender).
        LogBufferGlobal.setInstance(new LogBufferInterceptor(Integer.getInteger("logBuffer.history", 1024)));

        final Logger log = LoggerFactory.getLogger(mainClass);

        log.info().append("Starting up ").append(mainClass.getName()).append("...").endl();

        // Don't load up configuration until after logging has been initialized
        final Configuration config = Configuration.getInstance();

        // After logging and config are working, redirect any future JUL logging to SLF4J
        SLF4JBridgeHandler.removeHandlersForRootLogger();
        SLF4JBridgeHandler.install();

        // Push our log to ProcessEnvironment, so that any parts of the system relying on ProcessEnvironment
        // instead of LoggerFactory can get the correct logger.
        final ProcessEnvironment processEnvironment =
                ProcessEnvironment.basicInteractiveProcessInitialization(config, mainClass.getName(), log);
        Thread.setDefaultUncaughtExceptionHandler(processEnvironment.getFatalErrorReporter());
        HeapDump.setupHeapDumpWithDefaults(config,
                (final RuntimeException unused) -> ConstructSnapshot.concurrentAttemptInconsistent(), log);
        return config;
    }

    /**
     * Parses the configuration properties {@value SSL_IDENTITY_TYPE}, {@value SSL_IDENTITY_CERT_CHAIN_PATH},
     * {@value SSL_IDENTITY_PRIVATE_KEY_PATH}, {@value SSL_TRUST_TYPE}, {@value SSL_TRUST_PATH}, and
     * {@value SSL_CLIENT_AUTH}. Currently, the only valid identity type is {@value PRIVATEKEY}, and the only valid
     * trust type is {@value CERTS}. If no identity is present, empty will be returned.
     *
     * @param config the config
     * @return the optional SSL config
     */
    public static Optional parseSSLConfig(Configuration config) {
        return parseSSLConfig(null, config, true);
    }

    /**
     * Parser the same properties as {@link #parseSSLConfig(Configuration)}, except with property keys prefixed with
     * "outbound.". Identity configuration is not required.
     *
     * @param config the config
     * @return the optional outbound SSL config
     */
    public static Optional parseOutboundSSLConfig(Configuration config) {
        return parseSSLConfig("outbound.", config, false);
    }

    private static Optional parseSSLConfig(String prefix, Configuration config, boolean needsIdentity) {
        final Optional identity = parseIdentityConfig(prefix, config);
        if (needsIdentity && identity.isEmpty()) {
            return Optional.empty();
        }
        final SSLConfig.Builder builder = SSLConfig.builder();
        identity.ifPresent(builder::identity);
        parseTrustConfig(prefix, config).ifPresent(builder::trust);
        parseClientAuth(prefix, config).ifPresent(builder::clientAuthentication);
        return Optional.of(builder.build());
    }

    private static Optional parseIdentityConfig(String prefix, Configuration config) {
        final String identityType = config.getStringWithDefault(prefix(prefix, SSL_IDENTITY_TYPE), null);
        if (identityType == null) {
            return Optional.empty();
        }
        if (!PRIVATEKEY.equals(identityType)) {
            throw new IllegalArgumentException(
                    String.format("Only support `%s` identity type through Configuration", PRIVATEKEY));
        }
        final String identityCa = config.getStringWithDefault(prefix(prefix, SSL_IDENTITY_CERT_CHAIN_PATH), null);
        final String identityKey = config.getStringWithDefault(prefix(prefix, SSL_IDENTITY_PRIVATE_KEY_PATH), null);
        if (identityCa == null || identityKey == null) {
            throw new IllegalArgumentException(String.format("Must specify `%s` and `%s`",
                    prefix(prefix, SSL_IDENTITY_CERT_CHAIN_PATH), prefix(prefix, SSL_IDENTITY_PRIVATE_KEY_PATH)));
        }
        return Optional.of(IdentityPrivateKey.builder().certChainPath(identityCa).privateKeyPath(identityKey).build());
    }

    private static Optional parseTrustConfig(String prefix, Configuration config) {
        final String trustType = config.getStringWithDefault(prefix(prefix, SSL_TRUST_TYPE), null);
        if (trustType == null) {
            return Optional.empty();
        }
        if (!CERTS.equals(trustType)) {
            throw new IllegalArgumentException(
                    String.format("Only support `%s` trust type through Configuration", CERTS));
        }
        final String trustPath = config.getStringWithDefault(prefix(prefix, SSL_TRUST_PATH), null);
        return Optional.ofNullable(trustPath)
                .map(TrustCertificates::of)
                .or(() -> {
                    throw new IllegalArgumentException(
                            String.format("Must specify `%s`", prefix(prefix, SSL_TRUST_PATH)));
                });
    }

    private static Optional parseClientAuth(String prefix, Configuration config) {
        final String clientAuth = config.getStringWithDefault(prefix(prefix, SSL_CLIENT_AUTH), null);
        return Optional.ofNullable(clientAuth).map(ClientAuth::valueOf);
    }

    private static String prefix(String prefix, String key) {
        return prefix != null ? prefix + key : key;
    }

    private static Optional applicationProperty() {
        return Optional.ofNullable(System.getProperty(DEEPHAVEN_APPLICATION_PROPERTY));
    }

    private static Optional applicationEnvironmentVariable() {
        return Optional.ofNullable(System.getenv(DEEPHAVEN_APPLICATION_ENV));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy