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

io.quarkus.rest.client.reactive.runtime.RestClientCDIDelegateBuilder Maven / Gradle / Ivy

There is a newer version: 3.17.0.CR1
Show newest version
package io.quarkus.rest.client.reactive.runtime;

import static io.quarkus.rest.client.reactive.runtime.Constants.DEFAULT_MAX_CHUNK_SIZE;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import javax.net.ssl.HostnameVerifier;

import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.rest.client.ext.QueryParamStyle;
import org.jboss.resteasy.reactive.client.api.QuarkusRestClientProperties;
import org.jboss.resteasy.reactive.client.impl.multipart.PausableHttpPostRequestEncoder;

import io.quarkus.arc.Arc;
import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder;
import io.quarkus.restclient.config.RestClientsConfig;
import io.quarkus.runtime.configuration.MemorySize;
import io.quarkus.tls.TlsConfiguration;
import io.quarkus.tls.TlsConfigurationRegistry;
import io.smallrye.config.SmallRyeConfig;

public class RestClientCDIDelegateBuilder {

    private static final String REST_URL_FORMAT = "quarkus.rest-client.%s.url";
    private static final String REST_URI_FORMAT = "quarkus.rest-client.%s.uri";
    private static final String NONE = "none";

    private final Class jaxrsInterface;
    private final String baseUriFromAnnotation;
    private final String configKey;
    private final RestClientsConfig configRoot;

    public static  T createDelegate(Class jaxrsInterface, String baseUriFromAnnotation, String configKey) {
        return new RestClientCDIDelegateBuilder(jaxrsInterface, baseUriFromAnnotation, configKey).build();
    }

    private RestClientCDIDelegateBuilder(Class jaxrsInterface, String baseUriFromAnnotation, String configKey) {
        this(jaxrsInterface, baseUriFromAnnotation, configKey,
                ConfigProvider.getConfig().unwrap(SmallRyeConfig.class).getConfigMapping(RestClientsConfig.class));
    }

    RestClientCDIDelegateBuilder(Class jaxrsInterface, String baseUriFromAnnotation, String configKey,
            RestClientsConfig configRoot) {
        this.jaxrsInterface = jaxrsInterface;
        this.baseUriFromAnnotation = baseUriFromAnnotation;
        this.configKey = configKey;
        this.configRoot = configRoot;
    }

    private T build() {
        QuarkusRestClientBuilder builder = QuarkusRestClientBuilder.newBuilder();
        configureBuilder(builder);
        return builder.build(jaxrsInterface);
    }

    void configureBuilder(QuarkusRestClientBuilder builder) {
        configureBaseUrl(builder);
        configureTimeouts(builder);
        configureProviders(builder);
        configureTLS(builder);
        configureRedirects(builder);
        configureQueryParamStyle(builder);
        configureProxy(builder);
        configureShared(builder);
        configureCustomProperties(builder);
    }

    private void configureCustomProperties(QuarkusRestClientBuilder builder) {
        Optional encoder = oneOf(clientConfigByClassName().multipartPostEncoderMode(),
                clientConfigByConfigKey().multipartPostEncoderMode(), configRoot.multipartPostEncoderMode());
        if (encoder != null && encoder.isPresent()) {
            PausableHttpPostRequestEncoder.EncoderMode mode = PausableHttpPostRequestEncoder.EncoderMode
                    .valueOf(encoder.get().toUpperCase(Locale.ROOT));
            builder.property(QuarkusRestClientProperties.MULTIPART_ENCODER_MODE, mode);
        }

        Optional poolSize = oneOf(clientConfigByClassName().connectionPoolSize(),
                clientConfigByConfigKey().connectionPoolSize(), configRoot.connectionPoolSize());
        if (poolSize.isPresent()) {
            builder.property(QuarkusRestClientProperties.CONNECTION_POOL_SIZE, poolSize.get());
        }

        Optional connectionTTL = oneOf(clientConfigByClassName().connectionTTL(),
                clientConfigByConfigKey().connectionTTL(), configRoot.connectionTTL());
        if (connectionTTL.isPresent()) {
            // configuration bean contains value in milliseconds
            int connectionTTLSeconds = connectionTTL.get() / 1000;
            builder.property(QuarkusRestClientProperties.CONNECTION_TTL, connectionTTLSeconds);
        }

        Optional keepAliveEnabled = oneOf(clientConfigByClassName().keepAliveEnabled(),
                clientConfigByConfigKey().keepAliveEnabled(), configRoot.keepAliveEnabled());
        if (keepAliveEnabled.isPresent()) {
            builder.property(QuarkusRestClientProperties.KEEP_ALIVE_ENABLED, keepAliveEnabled.get());
        }

        Map headers = clientConfigByClassName().headers();
        if (headers == null || headers.isEmpty()) {
            headers = clientConfigByConfigKey().headers();
        }
        if (headers == null || headers.isEmpty()) {
            headers = configRoot.headers();
        }
        if (headers != null && !headers.isEmpty()) {
            builder.property(QuarkusRestClientProperties.STATIC_HEADERS, headers);
        }

        builder.property(QuarkusRestClientProperties.DISABLE_CONTEXTUAL_ERROR_MESSAGES,
                configRoot.disableContextualErrorMessages());

        Optional userAgent = oneOf(clientConfigByClassName().userAgent(),
                clientConfigByConfigKey().userAgent(), configRoot.userAgent());
        if (userAgent.isPresent()) {
            builder.userAgent(userAgent.get());
        }

        Optional maxChunkSize = oneOf(
                clientConfigByClassName().maxChunkSize().map(intChunkSize()),
                clientConfigByClassName().multipart().maxChunkSize(),
                clientConfigByConfigKey().maxChunkSize().map(intChunkSize()),
                clientConfigByConfigKey().multipart().maxChunkSize(),
                configRoot.maxChunkSize().map(intChunkSize()),
                configRoot.multipart().maxChunkSize());
        builder.property(QuarkusRestClientProperties.MAX_CHUNK_SIZE, maxChunkSize.orElse(DEFAULT_MAX_CHUNK_SIZE));

        Boolean http2 = oneOf(clientConfigByClassName().http2(),
                clientConfigByConfigKey().http2()).orElse(configRoot.http2());
        builder.property(QuarkusRestClientProperties.HTTP2, http2);

        Optional alpn = oneOf(clientConfigByClassName().alpn(),
                clientConfigByConfigKey().alpn(), configRoot.alpn());
        if (alpn.isPresent()) {
            builder.property(QuarkusRestClientProperties.ALPN, alpn.get());
        }

        Boolean captureStacktrace = oneOf(clientConfigByClassName().captureStacktrace(),
                clientConfigByConfigKey().captureStacktrace()).orElse(configRoot.captureStacktrace());
        builder.property(QuarkusRestClientProperties.CAPTURE_STACKTRACE, captureStacktrace);
    }

    private static Function intChunkSize() {
        return m -> (int) m.asLongValue();
    }

    private void configureProxy(QuarkusRestClientBuilder builder) {
        Optional maybeProxy = oneOf(clientConfigByClassName().proxyAddress(), clientConfigByConfigKey().proxyAddress(),
                configRoot.proxyAddress());
        if (maybeProxy.isEmpty()) {
            return;
        }

        String proxyAddress = maybeProxy.get();
        if (proxyAddress.equals("none")) {
            builder.proxyAddress("none", 0);
        } else {
            ProxyAddressUtil.HostAndPort hostAndPort = ProxyAddressUtil.parseAddress(proxyAddress);
            builder.proxyAddress(hostAndPort.host, hostAndPort.port);

            oneOf(clientConfigByClassName().proxyUser(), clientConfigByConfigKey().proxyUser(), configRoot.proxyUser())
                    .ifPresent(builder::proxyUser);
            oneOf(clientConfigByClassName().proxyPassword(), clientConfigByConfigKey().proxyPassword(),
                    configRoot.proxyPassword())
                    .ifPresent(builder::proxyPassword);
            oneOf(clientConfigByClassName().nonProxyHosts(), clientConfigByConfigKey().nonProxyHosts(),
                    configRoot.nonProxyHosts())
                    .ifPresent(builder::nonProxyHosts);
        }
    }

    private void configureQueryParamStyle(QuarkusRestClientBuilder builder) {
        Optional maybeQueryParamStyle = oneOf(clientConfigByClassName().queryParamStyle(),
                clientConfigByConfigKey().queryParamStyle(), configRoot.queryParamStyle());
        if (maybeQueryParamStyle.isPresent()) {
            QueryParamStyle queryParamStyle = maybeQueryParamStyle.get();
            builder.queryParamStyle(queryParamStyle);
        }
    }

    private void configureRedirects(QuarkusRestClientBuilder builder) {
        Optional maxRedirects = oneOf(clientConfigByClassName().maxRedirects(),
                clientConfigByConfigKey().maxRedirects(), configRoot.maxRedirects());
        if (maxRedirects.isPresent()) {
            builder.property(QuarkusRestClientProperties.MAX_REDIRECTS, maxRedirects.get());
        }

        Optional maybeFollowRedirects = oneOf(clientConfigByClassName().followRedirects(),
                clientConfigByConfigKey().followRedirects(), configRoot.followRedirects());
        if (maybeFollowRedirects.isPresent()) {
            builder.followRedirects(maybeFollowRedirects.get());
        }
    }

    private void configureShared(QuarkusRestClientBuilder builder) {
        Optional shared = oneOf(clientConfigByClassName().shared(),
                clientConfigByConfigKey().shared());
        if (shared.isPresent()) {
            builder.property(QuarkusRestClientProperties.SHARED, shared.get());

            if (shared.get()) {
                // Name is only used if shared = true
                Optional name = oneOf(clientConfigByClassName().name(),
                        clientConfigByConfigKey().name());
                if (name.isPresent()) {
                    builder.property(QuarkusRestClientProperties.NAME, name.get());
                }
            }
        }
    }

    private void configureTLS(QuarkusRestClientBuilder builder) {
        Optional maybeConfiguration = resolveTlsConfigurationForRegistry();
        if (maybeConfiguration.isPresent()) {
            builder.tlsConfiguration(maybeConfiguration.get());
        } else {
            configureTLSFromProperties(builder);
        }
    }

    private Optional resolveTlsConfigurationForRegistry() {
        if (Arc.container() != null) {
            var registry = Arc.container().select(TlsConfigurationRegistry.class).orNull();
            if (registry != null) {
                Optional maybeTlsConfigurationName = oneOf(clientConfigByClassName().tlsConfigurationName(),
                        clientConfigByConfigKey().tlsConfigurationName(),
                        configRoot.tlsConfigurationName());
                return TlsConfiguration.from(registry, maybeTlsConfigurationName);
            }
        }
        return Optional.empty();
    }

    private void configureTLSFromProperties(QuarkusRestClientBuilder builder) {
        Optional maybeTrustStore = oneOf(clientConfigByClassName().trustStore(), clientConfigByConfigKey().trustStore(),
                configRoot.trustStore());
        if (maybeTrustStore.isPresent() && !maybeTrustStore.get().isBlank() && !NONE.equals(maybeTrustStore.get())) {
            registerTrustStore(maybeTrustStore.get(), builder);
        }

        Optional maybeKeyStore = oneOf(clientConfigByClassName().keyStore(), clientConfigByConfigKey().keyStore(),
                configRoot.keyStore());
        if (maybeKeyStore.isPresent() && !maybeKeyStore.get().isBlank() && !NONE.equals(maybeKeyStore.get())) {
            registerKeyStore(maybeKeyStore.get(), builder);
        }

        Optional maybeHostnameVerifier = oneOf(clientConfigByClassName().hostnameVerifier(),
                clientConfigByConfigKey().hostnameVerifier(), configRoot.hostnameVerifier());
        if (maybeHostnameVerifier.isPresent()) {
            registerHostnameVerifier(maybeHostnameVerifier.get(), builder);
        }

        oneOf(clientConfigByClassName().verifyHost(), clientConfigByConfigKey().verifyHost(), configRoot.verifyHost())
                .ifPresent(builder::verifyHost);
    }

    private void registerHostnameVerifier(String verifier, QuarkusRestClientBuilder builder) {
        try {
            Class verifierClass = Thread.currentThread().getContextClassLoader().loadClass(verifier);
            builder.hostnameVerifier((HostnameVerifier) verifierClass.getDeclaredConstructor().newInstance());
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(
                    "Could not find a public, no-argument constructor for the hostname verifier class " + verifier, e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("Could not find hostname verifier class " + verifier, e);
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(
                    "Failed to instantiate hostname verifier class " + verifier
                            + ". Make sure it has a public, no-argument constructor",
                    e);
        } catch (ClassCastException e) {
            throw new RuntimeException("The provided hostname verifier " + verifier + " is not an instance of HostnameVerifier",
                    e);
        }
    }

    private void registerKeyStore(String keyStorePath, QuarkusRestClientBuilder builder) {
        Optional keyStorePassword = oneOf(clientConfigByClassName().keyStorePassword(),
                clientConfigByConfigKey().keyStorePassword(), configRoot.keyStorePassword());
        Optional keyStoreType = oneOf(clientConfigByClassName().keyStoreType(),
                clientConfigByConfigKey().keyStoreType(), configRoot.keyStoreType());

        try {
            KeyStore keyStore = KeyStore.getInstance(keyStoreType.orElse("JKS"));
            if (keyStorePassword.isEmpty()) {
                throw new IllegalArgumentException("No password provided for keystore");
            }
            String password = keyStorePassword.get();

            try (InputStream input = locateStream(keyStorePath)) {
                keyStore.load(input, password.toCharArray());
            } catch (IOException | CertificateException | NoSuchAlgorithmException e) {
                throw new IllegalArgumentException("Failed to initialize trust store from classpath resource " + keyStorePath,
                        e);
            }

            builder.keyStore(keyStore, password);
        } catch (KeyStoreException e) {
            throw new IllegalArgumentException("Failed to initialize trust store from " + keyStorePath, e);
        }
    }

    private void registerTrustStore(String trustStorePath, QuarkusRestClientBuilder builder) {
        Optional maybeTrustStorePassword = oneOf(clientConfigByClassName().trustStorePassword(),
                clientConfigByConfigKey().trustStorePassword(), configRoot.trustStorePassword());
        Optional maybeTrustStoreType = oneOf(clientConfigByClassName().trustStoreType(),
                clientConfigByConfigKey().trustStoreType(), configRoot.trustStoreType());

        try {
            KeyStore trustStore = KeyStore.getInstance(maybeTrustStoreType.orElse("JKS"));
            if (maybeTrustStorePassword.isEmpty()) {
                throw new IllegalArgumentException("No password provided for truststore");
            }
            String password = maybeTrustStorePassword.get();

            try (InputStream input = locateStream(trustStorePath)) {
                trustStore.load(input, password.toCharArray());
            } catch (IOException | CertificateException | NoSuchAlgorithmException e) {
                throw new IllegalArgumentException("Failed to initialize trust store from classpath resource " + trustStorePath,
                        e);
            }

            builder.trustStore(trustStore, password);
        } catch (KeyStoreException e) {
            throw new IllegalArgumentException("Failed to initialize trust store from " + trustStorePath, e);
        }
    }

    private InputStream locateStream(String path) throws FileNotFoundException {
        if (path.startsWith("classpath:")) {
            path = path.replaceFirst("classpath:", "");
            InputStream resultStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
            if (resultStream == null) {
                resultStream = getClass().getResourceAsStream(path);
            }
            if (resultStream == null) {
                throw new IllegalArgumentException(
                        "Classpath resource " + path + " not found for MicroProfile Rest Client SSL configuration");
            }
            return resultStream;
        } else {
            if (path.startsWith("file:")) {
                path = path.replaceFirst("file:", "");
            }
            File certificateFile = new File(path);
            if (!certificateFile.isFile()) {
                throw new IllegalArgumentException(
                        "Certificate file: " + path + " not found for MicroProfile Rest Client SSL configuration");
            }
            return new FileInputStream(certificateFile);
        }
    }

    private void configureProviders(QuarkusRestClientBuilder builder) {
        Optional maybeProviders = oneOf(clientConfigByClassName().providers(), clientConfigByConfigKey().providers(),
                configRoot.providers());
        if (maybeProviders.isPresent()) {
            registerProviders(builder, maybeProviders.get());
        }
    }

    private void registerProviders(QuarkusRestClientBuilder builder, String providersAsString) {
        for (String s : providersAsString.split(",")) {
            builder.register(providerClassForName(s.trim()));
        }
    }

    private Class providerClassForName(String name) {
        try {
            return Class.forName(name, true, Thread.currentThread().getContextClassLoader());
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("Could not find provider class: " + name);
        }
    }

    private void configureTimeouts(QuarkusRestClientBuilder builder) {
        Long connectTimeout = oneOf(clientConfigByClassName().connectTimeout(),
                clientConfigByConfigKey().connectTimeout()).orElse(this.configRoot.connectTimeout());
        if (connectTimeout != null) {
            builder.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS);
        }

        Long readTimeout = oneOf(clientConfigByClassName().readTimeout(),
                clientConfigByConfigKey().readTimeout()).orElse(this.configRoot.readTimeout());
        if (readTimeout != null) {
            builder.readTimeout(readTimeout, TimeUnit.MILLISECONDS);
        }
    }

    private void configureBaseUrl(QuarkusRestClientBuilder builder) {
        Optional propertyOptional = oneOf(clientConfigByClassName().uri(),
                clientConfigByConfigKey().uri());

        if (propertyOptional.isEmpty()) {
            propertyOptional = oneOf(clientConfigByClassName().url(),
                    clientConfigByConfigKey().url());
        }
        if (((baseUriFromAnnotation == null) || baseUriFromAnnotation.isEmpty())
                && propertyOptional.isEmpty()) {
            String propertyPrefix = configKey != null ? configKey : "\"" + jaxrsInterface.getName() + "\"";
            throw new IllegalArgumentException(
                    String.format(
                            "Unable to determine the proper baseUrl/baseUri. " +
                                    "Consider registering using @RegisterRestClient(baseUri=\"someuri\"), @RegisterRestClient(configKey=\"orkey\"), "
                                    +
                                    "or by adding '%s' or '%s' to your Quarkus configuration",
                            String.format(REST_URL_FORMAT, propertyPrefix), String.format(REST_URI_FORMAT, propertyPrefix)));
        }
        String baseUrl = propertyOptional.orElse(baseUriFromAnnotation);

        try {
            builder.baseUri(new URI(baseUrl));
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException("The value of URL was invalid " + baseUrl, e);
        }
    }

    private RestClientsConfig.RestClientConfig clientConfigByConfigKey() {
        return this.configRoot.getClient(configKey);
    }

    private RestClientsConfig.RestClientConfig clientConfigByClassName() {
        return this.configRoot.getClient(jaxrsInterface);
    }

    @SafeVarargs
    private static  Optional oneOf(Optional... optionals) {
        for (Optional o : optionals) {
            if (o != null && o.isPresent()) {
                return o;
            }
        }
        return Optional.empty();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy