HttpClientConfigImpl.java

/*
 * #%L
 * wcm.io
 * %%
 * Copyright (C) 2014 wcm.io
 * %%
 * 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.
 * #L%
 */
package io.wcm.caravan.commons.httpclient.impl;

import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.config.CookieSpecs;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
import org.osgi.service.metatype.annotations.Option;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.wcm.caravan.commons.httpclient.HttpClientConfig;
import io.wcm.caravan.commons.httpclient.impl.helpers.AbstractHttpClientConfig;
import io.wcm.caravan.commons.httpclient.impl.helpers.CertificateLoader;

/**
 * Default implementation of {@link HttpClientConfig}.
 */
@Component(service = HttpClientConfig.class, immediate = true, configurationPolicy = ConfigurationPolicy.REQUIRE,
    property = "webconsole.configurationFactory.nameHint={hostPatterns} {wsAddressingToUris} {pathPatterns}")
@Designate(ocd = HttpClientConfigImpl.Config.class, factory = true)
public class HttpClientConfigImpl extends AbstractHttpClientConfig {

  @ObjectClassDefinition(name = "wcm.io Caravan HTTP Client Configuration",
      description = "Allows to configure special HTTP client settings for target hosts.")
  @interface Config {

    /**
     * Host pattern
     */
    @AttributeDefinition(name = "Host pattern", description = "Regular expressions for matching the host name(s)")
    String[] hostPatterns();

    /**
     * WS Uri
     */
    @AttributeDefinition(name = "WS Uri", description = "List of WS Addressing To URIs for SOAP calls")
    String[] wsAddressingToUris();

    /**
     * Path pattern
     */
    @AttributeDefinition(name = "Path pattern", description = "Regular expressions for matching the path part of the target URLs")
    String[] pathPatterns();

    /**
     * Connection request timeout
     */
    @AttributeDefinition(name = "Connection request timeout", description = "Max. timeout to wait for getting a connection from the connection manager (ms)")
    int connectionRequestTimeout() default HttpClientConfig.CONNECTION_REQUEST_TIMEOUT_DEFAULT;

    /**
     * Connect timeout
     */
    @AttributeDefinition(name = "Connect timeout", description = "Max. timeout to wait for HTTP connection (ms)")
    int connectTimeout() default HttpClientConfig.CONNECT_TIMEOUT_DEFAULT;

    /**
     * Socket timeout
     */
    @AttributeDefinition(name = "Socket timeout", description = "Max. timeout to wait for a HTTP response (ms)")
    int socketTimeout() default HttpClientConfig.SOCKET_TIMEOUT_DEFAULT;

    /**
     * Max per host
     */
    @AttributeDefinition(name = "Max per host", description = "Max connections per host")
    int maxConnectionsPerHost() default HttpClientConfig.MAX_CONNECTIONS_PER_HOST_DEFAULT;

    /**
     * Max total
     */
    @AttributeDefinition(name = "Max total", description = "Max total connections")
    int maxTotalConnections() default HttpClientConfig.MAX_TOTAL_CONNECTIONS_DEFAULT;

    /**
     * Cookie Specs
     */
    @AttributeDefinition(name = "Cookie Spec", description = "Standard cookie specification for HttpClient. "
        + "See https://www.javadoc.io/static/org.apache.httpcomponents/httpclient/4.3.4/org/apache/http/client/config/CookieSpecs.html",
        options = {
            @Option(value = CookieSpecs.BROWSER_COMPATIBILITY, label = "BROWSER_COMPATIBILITY"),
            @Option(value = CookieSpecs.NETSCAPE, label = "NETSCAPE"),
            @Option(value = CookieSpecs.STANDARD, label = "STANDARD"),
            @Option(value = CookieSpecs.BEST_MATCH, label = "BEST_MATCH"),
            @Option(value = CookieSpecs.IGNORE_COOKIES, label = "IGNORE_COOKIES")
        })
    String cookieSpec() default HttpClientConfig.COOKIE_SPEC_DEFAULT;

    /**
     * Http user
     */
    @AttributeDefinition(name = "Http user", description = "User name for basic HTTP authentication")
    String httpUser();

    /**
     * Http password
     */
    @AttributeDefinition(name = "Http password", description = "Password for basic HTTP authentication")
    String httpPassword();

    /**
     * Proxy host
     */
    @AttributeDefinition(name = "Proxy host", description = "Proxy hostname")
    String proxyHost();

    /**
     * Proxy port
     */
    @AttributeDefinition(name = "Proxy port", description = "Proxy port")
    int proxyPort();

    /**
     * Proxy user
     */
    @AttributeDefinition(name = "Proxy user", description = "Proxy user name")
    String proxyUser();

    /**
     * Proxy password
     */
    @AttributeDefinition(name = "Proxy password", description = "Proxy password")
    String proxyPassword();

    /**
     * SSL context type
     */
    @AttributeDefinition(name = "SSL context type", description = "SSL context type")
    String sslContextType() default CertificateLoader.SSL_CONTEXT_TYPE_DEFAULT;

    /**
     * KeyManager type
     */
    @AttributeDefinition(name = "KeyManager type", description = "KeyManager type")
    String keyManagerType() default CertificateLoader.KEY_MANAGER_TYPE_DEFAULT;

    /**
     * KeyStore type
     */
    @AttributeDefinition(name = "KeyStore type", description = "KeyStore type")
    String keyStoreType() default CertificateLoader.KEY_STORE_TYPE_DEFAULT;

    /**
     * KeyStore provider
     */
    @AttributeDefinition(name = "KeyStore provider", description = "KeyStore provider. If not set the first matching security provider is used.")
    String keyStoreProvider();

    /**
     * KeyStore path
     */
    @AttributeDefinition(name = "KeyStore path", description = "KeyStore path")
    String keyStorePath();

    /**
     * KeyStore password
     */
    @AttributeDefinition(name = "KeyStore password", description = "KeyStore password")
    String keyStorePassword();

    /**
     * TrustManager type
     */
    @AttributeDefinition(name = "TrustManager type", description = "TrustManager type")
    String trustManagerType() default CertificateLoader.TRUST_MANAGER_TYPE_DEFAULT;

    /**
     * TrustStore type
     */
    @AttributeDefinition(name = "TrustStore type", description = "TrustStore type")
    String trustStoreType() default CertificateLoader.TRUST_STORE_TYPE_DEFAULT;

    /**
     * TrustStore provider
     */
    @AttributeDefinition(name = "TrustStore provider", description = "TrustStore provider. If not set the first matching security provider is used.")
    String trustStoreProvider();

    /**
     * TrustStore path
     */
    @AttributeDefinition(name = "TrustStore path", description = "TrustStore path")
    String trustStorePath();

    /**
     * TrustStore password
     */
    @AttributeDefinition(name = "TrustStore password", description = "TrustStore password")
    String trustStorePassword();

    /**
     * Enabled
     */
    @AttributeDefinition(name = "Enabled", description = "Enable this HTTP client configuration")
    boolean enabled() default true;

    /**
     * Service Ranking.
     */
    @AttributeDefinition(name = "Service Ranking",
        description = "Allows to define an order in which the HTTP client configurations are evaluated. Lower value = higher ranking.")
    @SuppressWarnings("java:S100") // property name is service.ranking
    int service_ranking();

    @SuppressWarnings("java:S100")
    String webconsole_configurationFactory_nameHint() default "{hostPatterns} {wsAddressingToUris} {pathPatterns}";

  }


  private boolean enabled;

  private int connectionRequestTimeout;
  private int connectTimeout;
  private int socketTimeout;
  private int maxConnectionsPerHost;
  private int maxTotalConnections;
  private String cookieSpec;
  private String httpUser;
  private String httpPassword;
  private String proxyHost;
  private int proxyPort;
  private String proxyUser;
  private String proxyPassword;
  private Set<Pattern> hostPatterns;
  private Set<String> wsAddressingToUris;
  private Set<Pattern> pathPatterns;

  private String sslContextType;
  private String keyManagerType;
  private String keyStoreType;
  private String keyStoreProvider;
  private String keyStorePath;
  private String keyStorePassword;
  private String trustManagerType;
  private String trustStoreType;
  private String trustStoreProvider;
  private String trustStorePath;
  private String trustStorePassword;

  private static final Logger log = LoggerFactory.getLogger(HttpClientConfigImpl.class);

  @Activate
  private void activate(Config config) {
    enabled = config.enabled();

    connectionRequestTimeout = config.connectionRequestTimeout();
    connectTimeout = config.connectTimeout();
    socketTimeout = config.socketTimeout();
    maxConnectionsPerHost = config.maxConnectionsPerHost();
    maxTotalConnections = config.maxTotalConnections();
    cookieSpec = config.cookieSpec();
    httpUser = config.httpUser();
    httpPassword = config.httpPassword();
    proxyHost = config.proxyHost();
    proxyPort = config.proxyPort();
    proxyUser = config.proxyUser();
    proxyPassword = config.proxyPassword();

    hostPatterns = new HashSet<>();
    String[] hostPatternsArray = config.hostPatterns();
    for (String hostPatternString : hostPatternsArray) {
      if (StringUtils.isNotBlank(hostPatternString)) {
        try {
          hostPatterns.add(Pattern.compile(hostPatternString));
        }
        catch (PatternSyntaxException ex) {
          log.warn("Invalid host name pattern '" + hostPatternString + "': " + ex.getMessage(), ex);
          this.enabled = false;
        }
      }
    }

    wsAddressingToUris = new HashSet<>();
    String[] wsAddressingToUrisArray = config.wsAddressingToUris();
    for (String wsAddressingToUriString : wsAddressingToUrisArray) {
      if (StringUtils.isNotBlank(wsAddressingToUriString)) {
        wsAddressingToUris.add(wsAddressingToUriString);
      }
    }

    pathPatterns = new HashSet<>();
    String[] pathPatternsArray = config.pathPatterns();
    for (String pathPatternString : pathPatternsArray) {
      if (StringUtils.isNotBlank(pathPatternString)) {
        try {
          pathPatterns.add(Pattern.compile(pathPatternString));
        }
        catch (PatternSyntaxException ex) {
          log.warn("Invalid path pattern '" + pathPatternString + "': " + ex.getMessage(), ex);
          this.enabled = false;
        }
      }
    }

    sslContextType = config.sslContextType();
    keyManagerType = config.keyManagerType();
    keyStoreType = config.keyStoreType();
    keyStoreProvider = config.keyStoreProvider();
    keyStorePath = config.keyStorePath();
    keyStorePassword = config.keyStorePassword();
    trustManagerType = config.trustManagerType();
    trustStoreType = config.trustStoreType();
    trustStoreProvider = config.trustStoreProvider();
    trustStorePath = config.trustStorePath();
    trustStorePassword = config.trustStorePassword();
  }

  @Override
  public boolean isEnabled() {
    return this.enabled;
  }

  @Override
  public int getConnectionRequestTimeout() {
    return connectionRequestTimeout;
  }

  @Override
  public int getConnectTimeout() {
    return connectTimeout;
  }

  @Override
  public int getSocketTimeout() {
    return socketTimeout;
  }

  @Override
  public int getMaxConnectionsPerHost() {
    return maxConnectionsPerHost;
  }

  @Override
  public int getMaxTotalConnections() {
    return maxTotalConnections;
  }

  @Override
  public @NotNull String getCookieSpec() {
    return cookieSpec;
  }

  @Override
  public @Nullable String getHttpUser() {
    return httpUser;
  }

  @Override
  public @Nullable String getHttpPassword() {
    return httpPassword;
  }

  @Override
  public @Nullable String getProxyHost() {
    return proxyHost;
  }

  @Override
  public int getProxyPort() {
    return proxyPort;
  }

  @Override
  public @Nullable String getProxyUser() {
    return proxyUser;
  }

  @Override
  public @Nullable String getProxyPassword() {
    return proxyPassword;
  }

  @Override
  public boolean matchesHost(@Nullable String host) {
    if (hostPatterns.isEmpty()) {
      return true;
    }
    if (StringUtils.isEmpty(host)) {
      return false;
    }
    for (Pattern hostPattern : hostPatterns) {
      if (hostPattern.matcher(host).matches()) {
        return true;
      }
    }
    return false;
  }

  @Override
  public boolean matchesWsAddressingToUri(@Nullable String addressingToUri) {
    if (wsAddressingToUris.isEmpty()) {
      return true;
    }
    if (StringUtils.isEmpty(addressingToUri)) {
      return false;
    }
    return wsAddressingToUris.contains(addressingToUri);
  }

  @Override
  public boolean matchesPath(@Nullable String path) {
    if (pathPatterns.isEmpty()) {
      return true;
    }
    if (StringUtils.isEmpty(path)) {
      return false;
    }
    for (Pattern pathPattern : pathPatterns) {
      if (pathPattern.matcher(path).matches()) {
        return true;
      }
    }
    return false;
  }

  @Override
  public @NotNull String getSslContextType() {
    return sslContextType;
  }

  @Override
  public @NotNull String getKeyManagerType() {
    return keyManagerType;
  }

  @Override
  public @NotNull String getKeyStoreType() {
    return keyStoreType;
  }

  @Override
  public @Nullable String getKeyStoreProvider() {
    return keyStoreProvider;
  }

  @Override
  public @Nullable String getKeyStorePath() {
    return keyStorePath;
  }

  @Override
  public @Nullable String getKeyStorePassword() {
    return keyStorePassword;
  }

  @Override
  public @NotNull String getTrustManagerType() {
    return trustManagerType;
  }

  @Override
  public @NotNull String getTrustStoreType() {
    return trustStoreType;
  }

  @Override
  public @Nullable String getTrustStoreProvider() {
    return trustStoreProvider;
  }

  @Override
  public @Nullable String getTrustStorePath() {
    return trustStorePath;
  }

  @Override
  public @Nullable String getTrustStorePassword() {
    return trustStorePassword;
  }

}