diff --git a/src/main/java/org/springframework/data/elasticsearch/client/ClientConfiguration.java b/src/main/java/org/springframework/data/elasticsearch/client/ClientConfiguration.java index 156bf43df..6481290fb 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/ClientConfiguration.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/ClientConfiguration.java @@ -26,7 +26,7 @@ import java.util.function.Supplier; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; -import org.springframework.http.HttpHeaders; +import org.springframework.data.elasticsearch.support.HttpHeaders; import org.springframework.lang.Nullable; import org.springframework.web.reactive.function.client.WebClient; diff --git a/src/main/java/org/springframework/data/elasticsearch/client/ClientConfigurationBuilder.java b/src/main/java/org/springframework/data/elasticsearch/client/ClientConfigurationBuilder.java index 50cf6b4be..ae5c41b2c 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/ClientConfigurationBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/ClientConfigurationBuilder.java @@ -22,7 +22,6 @@ import java.util.Arrays; import java.util.List; import java.util.function.Function; import java.util.function.Supplier; -import java.util.stream.Collectors; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; @@ -31,7 +30,7 @@ import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback; import org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationBuilderWithRequiredEndpoint; import org.springframework.data.elasticsearch.client.ClientConfiguration.MaybeSecureClientConfigurationBuilder; import org.springframework.data.elasticsearch.client.ClientConfiguration.TerminalClientConfigurationBuilder; -import org.springframework.http.HttpHeaders; +import org.springframework.data.elasticsearch.support.HttpHeaders; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.web.reactive.function.client.WebClient; @@ -50,7 +49,7 @@ class ClientConfigurationBuilder implements ClientConfigurationBuilderWithRequiredEndpoint, MaybeSecureClientConfigurationBuilder { private final List hosts = new ArrayList<>(); - private HttpHeaders headers = HttpHeaders.EMPTY; + private HttpHeaders headers = new HttpHeaders(); private boolean useSsl; private @Nullable SSLContext sslContext; private @Nullable HostnameVerifier hostnameVerifier; @@ -60,10 +59,10 @@ class ClientConfigurationBuilder private @Nullable String password; private @Nullable String pathPrefix; private @Nullable String proxy; - private Function webClientConfigurer = Function.identity(); - private Supplier headersSupplier = () -> HttpHeaders.EMPTY; - @Deprecated private HttpClientConfigCallback httpClientConfigurer = httpClientBuilder -> httpClientBuilder; - private List> clientConfigurers = new ArrayList<>(); + private final Function webClientConfigurer = Function.identity(); + private Supplier headersSupplier = HttpHeaders::new; + @Deprecated private final HttpClientConfigCallback httpClientConfigurer = httpClientBuilder -> httpClientBuilder; + private final List> clientConfigurers = new ArrayList<>(); /* * (non-Javadoc) @@ -74,7 +73,7 @@ class ClientConfigurationBuilder Assert.notEmpty(hostAndPorts, "At least one host is required"); - this.hosts.addAll(Arrays.stream(hostAndPorts).map(ClientConfigurationBuilder::parse).collect(Collectors.toList())); + this.hosts.addAll(Arrays.stream(hostAndPorts).map(ClientConfigurationBuilder::parse).toList()); return this; } @@ -227,9 +226,6 @@ class ClientConfigurationBuilder public ClientConfiguration build() { if (username != null && password != null) { - if (HttpHeaders.EMPTY.equals(headers)) { - headers = new HttpHeaders(); - } headers.setBasicAuth(username, password); } diff --git a/src/main/java/org/springframework/data/elasticsearch/client/ClientLogger.java b/src/main/java/org/springframework/data/elasticsearch/client/ClientLogger.java index 34e99edf8..da3dd60ac 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/ClientLogger.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/ClientLogger.java @@ -19,8 +19,6 @@ import java.util.function.Supplier; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; @@ -127,10 +125,10 @@ public abstract class ClientLogger { * @param logId the correlation id, see {@link #newLogId()}. * @param statusCode the HTTP status code. */ - public static void logRawResponse(String logId, @Nullable HttpStatusCode statusCode) { + public static void logRawResponse(String logId, @Nullable Integer statusCode) { if (isEnabled()) { - WIRE_LOGGER.trace(String.format("[%s] Received raw response: %s", logId, statusCode)); + WIRE_LOGGER.trace(String.format("[%s] Received raw response: %d", logId, statusCode)); } } @@ -141,10 +139,10 @@ public abstract class ClientLogger { * @param statusCode the HTTP status code. * @param headers a String containing the headers */ - public static void logRawResponse(String logId, @Nullable HttpStatus statusCode, String headers) { + public static void logRawResponse(String logId, @Nullable Integer statusCode, String headers) { if (isEnabled()) { - WIRE_LOGGER.trace(String.format("[%s] Received response: %s%n%s", logId, statusCode, headers)); + WIRE_LOGGER.trace(String.format("[%s] Received response: %d%n%s", logId, statusCode, headers)); } } @@ -155,10 +153,10 @@ public abstract class ClientLogger { * @param statusCode the HTTP status code. * @param body body content. */ - public static void logResponse(String logId, HttpStatusCode statusCode, String body) { + public static void logResponse(String logId, Integer statusCode, String body) { if (isEnabled()) { - WIRE_LOGGER.trace(String.format("[%s] Received response: %s%nResponse body: %s", logId, statusCode, body)); + WIRE_LOGGER.trace(String.format("[%s] Received response: %d%nResponse body: %s", logId, statusCode, body)); } } @@ -171,10 +169,10 @@ public abstract class ClientLogger { * @param body body content. * @since 4.4 */ - public static void logResponse(String logId, @Nullable HttpStatus statusCode, String headers, String body) { + public static void logResponse(String logId, @Nullable Integer statusCode, String headers, String body) { if (isEnabled()) { - WIRE_LOGGER.trace(String.format("[%s] Received response: %s%nHeaders: %s%nResponse body: %s", logId, statusCode, + WIRE_LOGGER.trace(String.format("[%s] Received response: %d%nHeaders: %s%nResponse body: %s", logId, statusCode, headers, body)); } } diff --git a/src/main/java/org/springframework/data/elasticsearch/client/DefaultClientConfiguration.java b/src/main/java/org/springframework/data/elasticsearch/client/DefaultClientConfiguration.java index 17f573f3e..35569eb23 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/DefaultClientConfiguration.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/DefaultClientConfiguration.java @@ -26,7 +26,7 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback; -import org.springframework.http.HttpHeaders; +import org.springframework.data.elasticsearch.support.HttpHeaders; import org.springframework.lang.Nullable; import org.springframework.web.reactive.function.client.WebClient; @@ -62,7 +62,7 @@ class DefaultClientConfiguration implements ClientConfiguration { List> clientConfigurers, Supplier headersSupplier) { this.hosts = List.copyOf(hosts); - this.headers = new HttpHeaders(headers); + this.headers = headers; this.useSsl = useSsl; this.sslContext = sslContext; this.soTimeout = soTimeout; @@ -127,7 +127,6 @@ class DefaultClientConfiguration implements ClientConfiguration { return webClientConfigurer; } - @SuppressWarnings("unchecked") @Override public List> getClientConfigurers() { return clientConfigurers; diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchClients.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchClients.java index e30c151ea..24cc1da79 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchClients.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchClients.java @@ -52,8 +52,7 @@ import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClientBuilder; import org.springframework.data.elasticsearch.client.ClientConfiguration; import org.springframework.data.elasticsearch.client.ClientLogger; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; +import org.springframework.data.elasticsearch.support.HttpHeaders; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -336,7 +335,7 @@ public final class ElasticsearchClients { // no way of logging the body, in this callback, it is not read yset, later there is no callback possibility in // RestClient or RestClientTransport - ClientLogger.logRawResponse(logId, HttpStatus.resolve(response.getStatusLine().getStatusCode()), headers); + ClientLogger.logRawResponse(logId, response.getStatusLine().getStatusCode(), headers); } } @@ -357,7 +356,7 @@ public final class ElasticsearchClients { public void process(HttpRequest request, HttpContext context) { HttpHeaders httpHeaders = headersSupplier.get(); - if (httpHeaders != null && httpHeaders != HttpHeaders.EMPTY) { + if (httpHeaders != null && !httpHeaders.isEmpty()) { Arrays.stream(toHeaderArray(httpHeaders)).forEach(request::addHeader); } } diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchExceptionTranslator.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchExceptionTranslator.java index 6df81a575..98a344827 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchExceptionTranslator.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchExceptionTranslator.java @@ -31,7 +31,6 @@ import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.data.elasticsearch.NoSuchIndexException; import org.springframework.data.elasticsearch.UncategorizedElasticsearchException; -import org.springframework.http.HttpStatus; /** * Simple {@link PersistenceExceptionTranslator} for Elasticsearch. Convert the given runtime exception to an @@ -76,8 +75,7 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra ErrorResponse response = elasticsearchException.response(); - if (response.status() == HttpStatus.NOT_FOUND.value() - && "index_not_found_exception".equals(response.error().type())) { + if (response.status() == 404 && "index_not_found_exception".equals(response.error().type())) { // noinspection RegExpRedundantEscape Pattern pattern = Pattern.compile(".*no such index \\[(.*)\\]"); diff --git a/src/main/java/org/springframework/data/elasticsearch/client/erhlc/DefaultReactiveElasticsearchClient.java b/src/main/java/org/springframework/data/elasticsearch/client/erhlc/DefaultReactiveElasticsearchClient.java index 8ca0157fa..1405e68d1 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/erhlc/DefaultReactiveElasticsearchClient.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/erhlc/DefaultReactiveElasticsearchClient.java @@ -139,7 +139,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch private final HostProvider hostProvider; private final RequestCreator requestCreator; - private Supplier headersSupplier = () -> HttpHeaders.EMPTY; + private Supplier headersSupplier = org.springframework.data.elasticsearch.support.HttpHeaders::new; /** * Create a new {@link DefaultReactiveElasticsearchClient} using the given {@link HostProvider} to obtain server @@ -181,8 +181,10 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch Assert.notNull(headers, "HttpHeaders must not be null"); Assert.notEmpty(hosts, "Elasticsearch Cluster needs to consist of at least one host"); + var httpHeaders = new org.springframework.data.elasticsearch.support.HttpHeaders(); + httpHeaders.addAll(headers); ClientConfiguration clientConfiguration = ClientConfiguration.builder().connectedTo(hosts) - .withDefaultHeaders(headers).build(); + .withDefaultHeaders(httpHeaders).build(); return create(clientConfiguration); } @@ -226,7 +228,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch return client; } - public void setHeadersSupplier(Supplier headersSupplier) { + public void setHeadersSupplier(Supplier headersSupplier) { Assert.notNull(headersSupplier, "headersSupplier must not be null"); @@ -716,25 +718,25 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch if (RawActionResponse.class.equals(responseType)) { - ClientLogger.logRawResponse(logId, response.statusCode()); + ClientLogger.logRawResponse(logId, response.statusCode().value()); return Mono.just(responseType.cast(RawActionResponse.create(response))); } if (response.statusCode().is5xxServerError()) { - ClientLogger.logRawResponse(logId, response.statusCode()); + ClientLogger.logRawResponse(logId, response.statusCode().value()); return handleServerError(request, response); } if (response.statusCode().is4xxClientError()) { - ClientLogger.logRawResponse(logId, response.statusCode()); + ClientLogger.logRawResponse(logId, response.statusCode().value()); return handleClientError(logId, response, responseType); } return response.body(BodyExtractors.toMono(byte[].class)) // .map(it -> new String(it, StandardCharsets.UTF_8)) // - .doOnNext(it -> ClientLogger.logResponse(logId, response.statusCode(), it)) // + .doOnNext(it -> ClientLogger.logResponse(logId, response.statusCode().value(), it)) // .flatMap(content -> doDecode(response, responseType, content)); } @@ -811,7 +813,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch return response.body(BodyExtractors.toMono(byte[].class)) // .map(bytes -> new String(bytes, StandardCharsets.UTF_8)) // .flatMap(content -> contentOrError(content, mediaType, status)) // - .doOnNext(content -> ClientLogger.logResponse(logId, response.statusCode(), content)) // + .doOnNext(content -> ClientLogger.logResponse(logId, response.statusCode().value(), content)) // .flatMap(content -> doDecode(response, responseType, content)); } diff --git a/src/main/java/org/springframework/data/elasticsearch/client/erhlc/HostProvider.java b/src/main/java/org/springframework/data/elasticsearch/client/erhlc/HostProvider.java index 86679bd7d..ffb9c8494 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/erhlc/HostProvider.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/erhlc/HostProvider.java @@ -24,7 +24,7 @@ import java.util.function.Supplier; import org.springframework.data.elasticsearch.client.ElasticsearchHost; import org.springframework.data.elasticsearch.client.NoReachableHostException; -import org.springframework.http.HttpHeaders; +import org.springframework.data.elasticsearch.support.HttpHeaders; import org.springframework.util.Assert; import org.springframework.web.reactive.function.client.WebClient; diff --git a/src/main/java/org/springframework/data/elasticsearch/client/erhlc/RestClients.java b/src/main/java/org/springframework/data/elasticsearch/client/erhlc/RestClients.java index 090e39690..89b565cf0 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/erhlc/RestClients.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/erhlc/RestClients.java @@ -46,8 +46,7 @@ import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.client.RestHighLevelClientBuilder; import org.springframework.data.elasticsearch.client.ClientConfiguration; import org.springframework.data.elasticsearch.client.ClientLogger; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; +import org.springframework.data.elasticsearch.support.HttpHeaders; import org.springframework.util.Assert; /** @@ -219,7 +218,7 @@ public final class RestClients { @Override public void process(HttpResponse response, HttpContext context) { String logId = (String) context.getAttribute(RestClients.LOG_ID_ATTRIBUTE); - ClientLogger.logRawResponse(logId, HttpStatus.resolve(response.getStatusLine().getStatusCode())); + ClientLogger.logRawResponse(logId, response.getStatusLine().getStatusCode()); } } @@ -240,7 +239,7 @@ public final class RestClients { public void process(HttpRequest request, HttpContext context) { HttpHeaders httpHeaders = headersSupplier.get(); - if (httpHeaders != null && httpHeaders != HttpHeaders.EMPTY) { + if (httpHeaders != null && !httpHeaders.isEmpty()) { Arrays.stream(toHeaderArray(httpHeaders)).forEach(request::addHeader); } } diff --git a/src/main/java/org/springframework/data/elasticsearch/client/erhlc/WebClientProvider.java b/src/main/java/org/springframework/data/elasticsearch/client/erhlc/WebClientProvider.java index edc587406..95da8e784 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/erhlc/WebClientProvider.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/erhlc/WebClientProvider.java @@ -243,11 +243,12 @@ public interface WebClientProvider { }; provider = provider // - .withDefaultHeaders(clientConfiguration.getDefaultHeaders()) // + .withDefaultHeaders(HttpHeaders.readOnlyHttpHeaders(clientConfiguration.getDefaultHeaders())) // .withWebClientConfigurer(webClientConfigurer) // .withRequestConfigurer(requestHeadersSpec -> requestHeadersSpec // .headers(httpHeaders -> { - HttpHeaders suppliedHeaders = clientConfiguration.getHeadersSupplier().get(); + HttpHeaders suppliedHeaders = HttpHeaders + .readOnlyHttpHeaders(clientConfiguration.getHeadersSupplier().get()); if (suppliedHeaders != null && suppliedHeaders != HttpHeaders.EMPTY) { httpHeaders.addAll(suppliedHeaders); diff --git a/src/main/java/org/springframework/data/elasticsearch/support/HttpHeaders.java b/src/main/java/org/springframework/data/elasticsearch/support/HttpHeaders.java new file mode 100644 index 000000000..f80125fa0 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/support/HttpHeaders.java @@ -0,0 +1,215 @@ +/* + * Copyright 2022 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 + * + * https://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.springframework.data.elasticsearch.support; + +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.LinkedCaseInsensitiveMap; +import org.springframework.util.MultiValueMap; +import org.springframework.util.MultiValueMapAdapter; + +/** + * A simple class implementing HTTP headers as a MultiValueMap. This own implementation is necessary to remove the + * dependency to the class with the same name from org.springframework:spring-web. Under the hood is uses a + * {@link LinkedCaseInsensitiveMap}. + * + * @author Peter-Josef Meisch + * @since 5.0 + */ +public class HttpHeaders implements MultiValueMap { + + public static final String AUTHORIZATION = "Authorization"; + + private final MultiValueMap delegate; + + public HttpHeaders() { + this.delegate = new MultiValueMapAdapter<>(new LinkedCaseInsensitiveMap<>(Locale.ENGLISH)); + } + + // region MultiValueMap + @Override + @Nullable + public String getFirst(String key) { + return delegate.getFirst(key); + } + + @Override + public void add(String key, @Nullable String value) { + delegate.add(key, value); + } + + @Override + public void addAll(String key, List values) { + delegate.addAll(key, values); + } + + @Override + public void addAll(MultiValueMap values) { + delegate.addAll(values); + } + + @Override + public void set(String key, @Nullable String value) { + delegate.set(key, value); + } + + @Override + public void setAll(Map values) { + delegate.setAll(values); + } + + @Override + public Map toSingleValueMap() { + return delegate.toSingleValueMap(); + } + // endregion + + // region Map + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return delegate.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return delegate.containsValue(value); + } + + @Override + @Nullable + public List get(Object key) { + return delegate.get(key); + } + + @Nullable + @Override + public List put(String key, List value) { + return delegate.put(key, value); + } + + @Override + public List remove(Object key) { + return delegate.remove(key); + } + + @Override + public void putAll(Map> m) { + + Assert.notNull(m, "m must not be null"); + + delegate.putAll(m); + } + + @Override + public void clear() { + delegate.clear(); + } + + @Override + public Set keySet() { + return delegate.keySet(); + } + + @Override + public Collection> values() { + return delegate.values(); + } + + @Override + public Set>> entrySet() { + return delegate.entrySet(); + } + + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") + @Override + public boolean equals(Object o) { + return delegate.equals(o); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + // endregion + public void setBasicAuth(String username, String password) { + + Assert.notNull(username, "username must not be null"); + Assert.notNull(password, "password must not be null"); + + set(AUTHORIZATION, "Basic " + encodeBasicAuth(username, password)); + } + + /** + * Encode a username and password to be used in basic authorization. Code copied from the spring-web HttpHeaders + * class. + * + * @param username the username, must not contain a colon + * @param password the password + * @return the encoded value + */ + public static String encodeBasicAuth(String username, String password) { + return encodeBasicAuth(username, password, null); + } + + /** + * Encode a username and password to be used in basic authorization. Code copied from the spring-web HttpHeaders + * class. + * + * @param username the username, must not contain a colon + * @param password the password + * @param charset charset for the encoding, if {@literal null} StandardCharsets.ISO_8859_1 is used + * @return the encoded value + */ + public static String encodeBasicAuth(String username, String password, @Nullable Charset charset) { + Assert.notNull(username, "Username must not be null"); + Assert.doesNotContain(username, ":", "Username must not contain a colon"); + Assert.notNull(password, "Password must not be null"); + if (charset == null) { + charset = StandardCharsets.ISO_8859_1; + } + + CharsetEncoder encoder = charset.newEncoder(); + if (encoder.canEncode(username) && encoder.canEncode(password)) { + String credentialsString = username + ':' + password; + byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(charset)); + return new String(encodedBytes, charset); + } else { + throw new IllegalArgumentException( + "Username or password contains characters that cannot be encoded to " + charset.displayName()); + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/client/ClientConfigurationUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/client/ClientConfigurationUnitTests.java index 6298bdd93..4361af8e0 100644 --- a/src/test/java/org/springframework/data/elasticsearch/client/ClientConfigurationUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/client/ClientConfigurationUnitTests.java @@ -17,10 +17,10 @@ package org.springframework.data.elasticsearch.client; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; +import static org.springframework.data.elasticsearch.support.HttpHeaders.*; import java.net.InetSocketAddress; import java.time.Duration; -import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; @@ -32,7 +32,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.data.elasticsearch.client.erhlc.ReactiveRestClients; import org.springframework.data.elasticsearch.client.erhlc.RestClients; -import org.springframework.http.HttpHeaders; +import org.springframework.data.elasticsearch.support.HttpHeaders; import org.springframework.web.reactive.function.client.WebClient; /** @@ -45,6 +45,8 @@ import org.springframework.web.reactive.function.client.WebClient; */ public class ClientConfigurationUnitTests { + private static final String AUTHORIZATION = "Authorization"; + @Test // DATAES-488 public void shouldCreateSimpleConfiguration() { @@ -106,8 +108,8 @@ public class ClientConfigurationUnitTests { .withBasicAuth(username, password) // .build(); - assertThat(clientConfiguration.getDefaultHeaders().get(HttpHeaders.AUTHORIZATION)) - .containsOnly(buildBasicAuth(username, password)); + assertThat(clientConfiguration.getDefaultHeaders().get(AUTHORIZATION)) + .containsOnly("Basic " + encodeBasicAuth(username, password)); } @Test // DATAES-607 @@ -127,9 +129,9 @@ public class ClientConfigurationUnitTests { HttpHeaders httpHeaders = clientConfiguration.getDefaultHeaders(); - assertThat(httpHeaders.get(HttpHeaders.AUTHORIZATION)).containsOnly(buildBasicAuth(username, password)); + assertThat(httpHeaders.get(AUTHORIZATION)).containsOnly("Basic " + encodeBasicAuth(username, password)); assertThat(httpHeaders.getFirst("foo")).isEqualTo("bar"); - assertThat(defaultHeaders.get(HttpHeaders.AUTHORIZATION)).isNull(); + assertThat(defaultHeaders.get(AUTHORIZATION)).isNull(); } @Test // DATAES-673 @@ -219,14 +221,8 @@ public class ClientConfigurationUnitTests { ClientConfiguration.ClientConfigurationCallback clientConfigurer = clientConfiguration.getClientConfigurers() .get(0); + // noinspection unchecked ((ClientConfiguration.ClientConfigurationCallback) clientConfigurer).configure(new Object()); assertThat(callCounter.get()).isEqualTo(1); } - - private static String buildBasicAuth(String username, String password) { - - HttpHeaders headers = new HttpHeaders(); - headers.setBasicAuth(username, password); - return Objects.requireNonNull(headers.getFirst(HttpHeaders.AUTHORIZATION)); - } } diff --git a/src/test/java/org/springframework/data/elasticsearch/client/RestClientsTest.java b/src/test/java/org/springframework/data/elasticsearch/client/RestClientsTest.java index 9069fc1d3..4d301c044 100644 --- a/src/test/java/org/springframework/data/elasticsearch/client/RestClientsTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/client/RestClientsTest.java @@ -46,7 +46,7 @@ import org.springframework.data.elasticsearch.client.elc.ElasticsearchClients; import org.springframework.data.elasticsearch.client.elc.ReactiveElasticsearchClient; import org.springframework.data.elasticsearch.client.erhlc.ReactiveRestClients; import org.springframework.data.elasticsearch.client.erhlc.RestClients; -import org.springframework.http.HttpHeaders; +import org.springframework.data.elasticsearch.support.HttpHeaders; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; diff --git a/src/test/java/org/springframework/data/elasticsearch/client/elc/DevTests.java b/src/test/java/org/springframework/data/elasticsearch/client/elc/DevTests.java index 75c22ce5a..9ff84296a 100644 --- a/src/test/java/org/springframework/data/elasticsearch/client/elc/DevTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/client/elc/DevTests.java @@ -54,7 +54,7 @@ import org.junit.jupiter.api.TestMethodOrder; import org.springframework.data.elasticsearch.client.ClientConfiguration; import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter; import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; -import org.springframework.http.HttpHeaders; +import org.springframework.data.elasticsearch.support.HttpHeaders; import org.springframework.lang.Nullable; /** diff --git a/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ElasticsearchRestTemplateConfiguration.java b/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ElasticsearchRestTemplateConfiguration.java index aa8a109cc..9fbb940fd 100644 --- a/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ElasticsearchRestTemplateConfiguration.java +++ b/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ElasticsearchRestTemplateConfiguration.java @@ -31,7 +31,7 @@ import org.springframework.data.elasticsearch.client.erhlc.RestClients; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.RefreshPolicy; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; -import org.springframework.http.HttpHeaders; +import org.springframework.data.elasticsearch.support.HttpHeaders; /** * Configuration for Spring Data Elasticsearch using {@link ElasticsearchRestTemplate}. @@ -77,7 +77,7 @@ public class ElasticsearchRestTemplateConfiguration extends AbstractElasticsearc defaultHeaders.add("Accept", "application/vnd.elasticsearch+json;compatible-with=7"); defaultHeaders.add("Content-Type", "application/vnd.elasticsearch+json;compatible-with=7"); - //noinspection resource + // noinspection resource return RestClients.create(configurationBuilder // .withDefaultHeaders(defaultHeaders) // .withConnectTimeout(Duration.ofSeconds(20)) // diff --git a/src/test/java/org/springframework/data/elasticsearch/support/HttpHeadersTest.java b/src/test/java/org/springframework/data/elasticsearch/support/HttpHeadersTest.java new file mode 100644 index 000000000..a4954772e --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/support/HttpHeadersTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 2022 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 + * + * https://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.springframework.data.elasticsearch.support; + +import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.elasticsearch.support.HttpHeaders.*; + +import java.util.List; +import java.util.Locale; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * @author Peter-Josef Meisch + * @since 5.0 + */ +class HttpHeadersTest { + + public static final String X_TEST_HEADER = "X-Test-Header"; + + @Test // #2277 + @DisplayName("should find with case insensitive key") + void shouldFindWithCaseInsensitiveKey() { + + var httpHeaders = new HttpHeaders(); + httpHeaders.set(X_TEST_HEADER, "foo"); + + assertThat(httpHeaders.get(X_TEST_HEADER.toLowerCase(Locale.ENGLISH))).containsExactly("foo"); + } + + @Test // #2277 + @DisplayName("should overwrite values with set") + void shouldOverwriteValuesWithSet() { + var httpHeaders = new HttpHeaders(); + httpHeaders.add(X_TEST_HEADER, "foo"); + + httpHeaders.set(X_TEST_HEADER, "bar"); + + assertThat(httpHeaders.get(X_TEST_HEADER)).containsExactly("bar"); + } + + @Test // #2277 + @DisplayName("should set authentication header") + void shouldSetAuthenticationHeader() { + + var encodedAuth = encodeBasicAuth("foo", "bar"); + var httpHeaders = new HttpHeaders(); + httpHeaders.setBasicAuth("foo", "bar"); + + assertThat(httpHeaders.getFirst(AUTHORIZATION)).isEqualTo("Basic " + encodedAuth); + } + + @Test // #2277 + @DisplayName("should initialize from Spring HttpHeaders") + void shouldInitializeFromSpringHttpHeaders() { + + // we can use the Spring class in this test as we have spring-webflux as optional dependency and so have spring-web + // as well + + org.springframework.http.HttpHeaders springHttpHeaders = new org.springframework.http.HttpHeaders(); + springHttpHeaders.addAll(X_TEST_HEADER, List.of("foo", "bar")); + var headerName = "x-from-spring"; + springHttpHeaders.add(headerName, "true"); + + var httpHeaders = new HttpHeaders(); + httpHeaders.addAll(springHttpHeaders); + + assertThat(httpHeaders.get(X_TEST_HEADER)).containsExactly("foo", "bar"); + assertThat(httpHeaders.get(headerName)).containsExactly("true"); + } +}