keycloak-uncached

Details

diff --git a/services/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java b/services/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java
index 6cfeafd..7e6b4c1 100755
--- a/services/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java
+++ b/services/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java
@@ -36,11 +36,25 @@ import org.keycloak.truststore.TruststoreProvider;
 import java.io.IOException;
 import java.io.InputStream;
 import java.security.KeyStore;
-import java.util.Arrays;
-import java.util.Collections;
 import java.util.concurrent.TimeUnit;
 
 /**
+ * The default {@link HttpClientFactory} for {@link HttpClientProvider HttpClientProvider's} used by Keycloak for outbound HTTP calls.
+ * <p>
+ * The constructed clients can be configured via Keycloaks SPI configuration, e.g. {@code standalone.xml, standalone-ha.xml, domain.xml}.
+ * </p>
+ * <p>
+ * Examples for jboss-cli
+ * </p>
+ * <pre>
+ * {@code
+ *
+ * /subsystem=keycloak-server/spi=connectionsHttpClient/provider=default:add(enabled=true)
+ * /subsystem=keycloak-server/spi=connectionsHttpClient/provider=default:write-attribute(name=properties.connection-pool-size,value=128)
+ * /subsystem=keycloak-server/spi=connectionsHttpClient/provider=default:write-attribute(name=properties.proxy-mappings,value=[".*\\.(google|googleapis)\\.com;http://www-proxy.acme.corp.com:8080",".*\\.acme\\.corp\\.com;NO_PROXY",".*;http://fallback:8080"])
+ * }
+ * </pre>
+ * </p>
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
 public class DefaultHttpClientFactory implements HttpClientFactory {
@@ -148,7 +162,7 @@ public class DefaultHttpClientFactory implements HttpClientFactory {
                             .connectionTTL(connectionTTL, TimeUnit.MILLISECONDS)
                             .maxConnectionIdleTime(maxConnectionIdleTime, TimeUnit.MILLISECONDS)
                             .disableCookies(disableCookies)
-                            .proxyMapping(new ProxyMapping(proxyMappings == null ? Collections.emptyList() : Arrays.asList(proxyMappings)));
+                            .proxyMappings(ProxyMappings.valueOf(proxyMappings));
 
                     if (disableTrustManager) {
                         // TODO: is it ok to do away with disabling trust manager?
diff --git a/services/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java b/services/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java
index 22e8c58..e5bebf2 100755
--- a/services/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java
+++ b/services/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java
@@ -104,7 +104,7 @@ public class HttpClientBuilder {
     protected long establishConnectionTimeout = -1;
     protected TimeUnit establishConnectionTimeoutUnits = TimeUnit.MILLISECONDS;
     protected boolean disableCookies = false;
-    protected ProxyMapping proxyMapping;
+    protected ProxyMappings proxyMappings;
 
     /**
      * Socket inactivity timeout
@@ -208,8 +208,8 @@ public class HttpClientBuilder {
         return this;
     }
 
-    public HttpClientBuilder proxyMapping(ProxyMapping proxyMapping) {
-        this.proxyMapping = proxyMapping;
+    public HttpClientBuilder proxyMappings(ProxyMappings proxyMappings) {
+        this.proxyMappings = proxyMappings;
         return this;
     }
 
@@ -290,8 +290,8 @@ public class HttpClientBuilder {
                     .setConnectionTimeToLive(connectionTTL, connectionTTLUnit);
 
 
-            if (proxyMapping != null && !proxyMapping.isEmpty()) {
-                builder.setRoutePlanner(new ProxyMappingAwareRoutePlanner(proxyMapping));
+            if (proxyMappings != null && !proxyMappings.isEmpty()) {
+                builder.setRoutePlanner(new ProxyMappingsAwareRoutePlanner(proxyMappings));
             }
 
             if (maxConnectionIdleTime > 0) {
diff --git a/services/src/main/java/org/keycloak/connections/httpclient/ProxyMappings.java b/services/src/main/java/org/keycloak/connections/httpclient/ProxyMappings.java
new file mode 100644
index 0000000..c534cb8
--- /dev/null
+++ b/services/src/main/java/org/keycloak/connections/httpclient/ProxyMappings.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * 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 org.keycloak.connections.httpclient;
+
+import org.apache.http.HttpHost;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * {@link ProxyMappings} describes an ordered mapping for hostname regex patterns to a {@link HttpHost} proxy.
+ * <p>
+ * Mappings can be created via {@link #valueOf(String...)} or {@link #valueOf(List)}.
+ * For a description of the mapping format see {@link ProxyMapping#valueOf(String)}
+ *
+ * @author <a href="mailto:thomas.darimont@gmail.com">Thomas Darimont</a>
+ */
+public class ProxyMappings {
+
+  private static final ProxyMappings EMPTY_MAPPING = valueOf(Collections.emptyList());
+
+  private final List<ProxyMapping> entries;
+
+  /**
+   * Creates a {@link ProxyMappings} from the provided {@link ProxyMapping Entries}.
+   *
+   * @param entries
+   */
+  public ProxyMappings(List<ProxyMapping> entries) {
+    this.entries = Collections.unmodifiableList(entries);
+  }
+
+  /**
+   * Creates a new  {@link ProxyMappings} from the provided {@code List} of proxy mapping strings.
+   * <p>
+   *
+   * @param proxyMappings
+   */
+  public static ProxyMappings valueOf(List<String> proxyMappings) {
+
+    if (proxyMappings == null || proxyMappings.isEmpty()) {
+      return EMPTY_MAPPING;
+    }
+
+    List<ProxyMapping> entries = proxyMappings.stream() //
+      .map(ProxyMapping::valueOf) //
+      .collect(Collectors.toList());
+
+    return new ProxyMappings(entries);
+  }
+
+  /**
+   * Creates a new  {@link ProxyMappings} from the provided {@code String[]} of proxy mapping strings.
+   *
+   * @param proxyMappings
+   * @return
+   * @see #valueOf(List)
+   * @see ProxyMapping#valueOf(String...)
+   */
+  public static ProxyMappings valueOf(String... proxyMappings) {
+
+    if (proxyMappings == null || proxyMappings.length == 0) {
+      return EMPTY_MAPPING;
+    }
+
+    return valueOf(Arrays.asList(proxyMappings));
+  }
+
+
+  public boolean isEmpty() {
+    return this.entries.isEmpty();
+  }
+
+  /**
+   * @param hostname
+   * @return the {@link HttpHost} proxy associated with the first matching hostname {@link Pattern}
+   * or {@literal null} if none matches.
+   */
+  public HttpHost getProxyFor(String hostname) {
+
+    Objects.requireNonNull(hostname, "hostname");
+
+    return entries.stream() //
+      .filter(e -> e.matches(hostname)) //
+      .findFirst() //
+      .map(ProxyMapping::getProxy) //
+      .orElse(null);
+  }
+
+  /**
+   * {@link ProxyMapping} describes a Proxy Mapping with a Hostname {@link Pattern}
+   * that is mapped to a proxy {@link HttpHost}.
+   */
+  public static class ProxyMapping {
+
+    public static final String NO_PROXY = "NO_PROXY";
+    private static final String DELIMITER = ";";
+
+    private final Pattern hostnamePattern;
+
+    private final HttpHost proxy;
+
+    public ProxyMapping(Pattern hostnamePattern, HttpHost proxy) {
+      this.hostnamePattern = hostnamePattern;
+      this.proxy = proxy;
+    }
+
+    public Pattern getHostnamePattern() {
+      return hostnamePattern;
+    }
+
+    public HttpHost getProxy() {
+      return proxy;
+    }
+
+    public boolean matches(String hostname) {
+      return getHostnamePattern().matcher(hostname).matches();
+    }
+
+    /**
+     * Parses a mapping string into an {@link ProxyMapping}.
+     * <p>
+     * A proxy mapping string must have the following format: {@code hostnameRegex;www-proxy-uri}
+     * with semicolon as a delimiter.</p>
+     * <p>
+     * If no proxy should be used for a host pattern then use {@code NO_PROXY} as www-proxy-uri.
+     * </p>
+     * <p>Examples:
+     * <pre>
+     * {@code
+     *
+     * .*\.(google\.com|googleapis\.com);http://www-proxy.acme.corp.com:8080
+     * .*\.acme\.corp\.com;NO_PROXY
+     * .*;http://fallback:8080
+     * }
+     * </pre>
+     * </p>
+     *
+     * @param mapping
+     * @return
+     */
+    public static ProxyMapping valueOf(String mapping) {
+
+      String[] mappingTokens = mapping.split(DELIMITER);
+
+      String hostPatternRegex = mappingTokens[0];
+      String proxyUriString = mappingTokens[1];
+
+      Pattern hostPattern = Pattern.compile(hostPatternRegex);
+      HttpHost proxyHost = toProxyHost(proxyUriString);
+
+      return new ProxyMapping(hostPattern, proxyHost);
+    }
+
+    private static HttpHost toProxyHost(String proxyUriString) {
+
+      if (NO_PROXY.equals(proxyUriString)) {
+        return null;
+      }
+
+      URI uri = URI.create(proxyUriString);
+      return new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
+    }
+
+    @Override
+    public String toString() {
+      return "ProxyMapping{" +
+        "hostnamePattern=" + hostnamePattern +
+        ", proxy=" + proxy +
+        '}';
+    }
+  }
+}