Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
58e96b1
HSEARCH-5464 Remove the apache host from ElasticsearchResponse
marko-bekhta Aug 27, 2025
0484698
HSEARCH-5464 Move out client common classes
marko-bekhta Aug 27, 2025
8438a68
HSEARCH-5464 Remove dependency on ES version class in the client code
marko-bekhta Aug 27, 2025
756dd0a
HSEARCH-5464 Continue factoring out the Client
marko-bekhta Sep 1, 2025
f74581d
HSEARCH-5464 OpenSearch client
marko-bekhta Sep 3, 2025
27541fe
HSEARCH-5464 Elasticsearch-java client
marko-bekhta Sep 4, 2025
21cf9b4
HSEARCH-5464 Test different clients
marko-bekhta Sep 4, 2025
e5d9e5e
HSEARCH-5464 Rename the clients
marko-bekhta Sep 4, 2025
c141536
HSEARCH-5464 Adjust elasticsearch-java tests
marko-bekhta Sep 5, 2025
e33dbee
HSEARCH-5464 Fix aggregated javadocs and dependency alignment
marko-bekhta Sep 5, 2025
4f36c56
HSEARCH-5464 More renames to address JQAssistant rules
marko-bekhta Sep 5, 2025
ec5c0e5
HSEARCH-5464 Explicitly list the apache http client libs we use to im…
marko-bekhta Sep 5, 2025
16f0121
HSEARCH-5464 Push client specific settings to client modules
marko-bekhta Sep 9, 2025
393736b
HSEARCH-5464 Make AWS signing compatible with other client impls
marko-bekhta Sep 9, 2025
73c067a
HSEARCH-5464 Adjust search-util-common exports
marko-bekhta Sep 9, 2025
a3e54be
HSEARCH-5464 Add missing platform dependencies
marko-bekhta Oct 13, 2025
b83183c
HSEARCH-5464 Add a few notes on the new pluggable clients
marko-bekhta Oct 16, 2025
1d0d3bf
HSEARCH-5464 Move (most of) the elasticsearch-client-rest back into …
marko-bekhta Oct 23, 2025
98bbb78
HSEARCH-5464 Address some of the review comments
marko-bekhta Oct 23, 2025
4eb86b4
HSEARCH-5464 Switch to the new elasticsearch-rest5-client
marko-bekhta Oct 24, 2025
68c6fab
HSEARCH-5464 Rename Elasticsearch clients and their packages
marko-bekhta Oct 24, 2025
1ba07a2
HSEARCH-5464 Clarify a few points in the docs
marko-bekhta Oct 24, 2025
8a4937a
HSEARCH-5464 Mark rest5 and opensearch clients as incubating
marko-bekhta Oct 24, 2025
b805101
HSEARCH-5464 Finish with renaming and JQAssistant
marko-bekhta Oct 24, 2025
3152541
HSEARCH-5464 Move around gson entity to keep the common code in the b…
marko-bekhta Oct 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import org.hibernate.search.backend.elasticsearch.aws.cfg.ElasticsearchAwsCredentialsTypeNames;
import org.hibernate.search.backend.elasticsearch.aws.spi.ElasticsearchAwsCredentialsProvider;
import org.hibernate.search.backend.elasticsearch.client.ElasticsearchHttpClientConfigurer;
import org.hibernate.search.backend.elasticsearch.client.common.spi.ElasticsearchRequestInterceptorProvider;
import org.hibernate.search.engine.environment.bean.BeanHolder;
import org.hibernate.search.engine.environment.bean.spi.BeanConfigurationContext;
import org.hibernate.search.engine.environment.bean.spi.BeanConfigurer;
Expand All @@ -17,8 +17,8 @@ public class ElasticsearchAwsBeanConfigurer implements BeanConfigurer {
@Override
public void configure(BeanConfigurationContext context) {
context.define(
ElasticsearchHttpClientConfigurer.class,
beanResolver -> BeanHolder.of( new ElasticsearchAwsHttpClientConfigurer() )
ElasticsearchRequestInterceptorProvider.class,
beanResolver -> BeanHolder.of( new ElasticsearchAwsSigningInterceptorProvider() )
);
context.define(
ElasticsearchAwsCredentialsProvider.class, ElasticsearchAwsCredentialsTypeNames.DEFAULT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@
*/
package org.hibernate.search.backend.elasticsearch.aws.impl;

import org.hibernate.search.backend.elasticsearch.ElasticsearchDistributionName;
import org.hibernate.search.backend.elasticsearch.ElasticsearchVersion;
import java.util.Locale;
import java.util.Optional;
import java.util.regex.Pattern;

import org.hibernate.search.backend.elasticsearch.aws.cfg.ElasticsearchAwsBackendSettings;
import org.hibernate.search.backend.elasticsearch.aws.cfg.ElasticsearchAwsCredentialsTypeNames;
import org.hibernate.search.backend.elasticsearch.aws.logging.impl.AwsLog;
import org.hibernate.search.backend.elasticsearch.aws.spi.ElasticsearchAwsCredentialsProvider;
import org.hibernate.search.backend.elasticsearch.client.ElasticsearchHttpClientConfigurationContext;
import org.hibernate.search.backend.elasticsearch.client.ElasticsearchHttpClientConfigurer;
import org.hibernate.search.backend.elasticsearch.client.common.spi.ElasticsearchRequestInterceptor;
import org.hibernate.search.backend.elasticsearch.client.common.spi.ElasticsearchRequestInterceptorProvider;
import org.hibernate.search.backend.elasticsearch.client.common.spi.ElasticsearchRequestInterceptorProviderContext;
import org.hibernate.search.engine.cfg.ConfigurationPropertySource;
import org.hibernate.search.engine.cfg.spi.ConfigurationProperty;
import org.hibernate.search.engine.cfg.spi.OptionalConfigurationProperty;
Expand All @@ -22,8 +25,8 @@
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.regions.Region;

public class ElasticsearchAwsHttpClientConfigurer implements ElasticsearchHttpClientConfigurer {

public class ElasticsearchAwsSigningInterceptorProvider implements ElasticsearchRequestInterceptorProvider {
private static final Pattern DISTRIBUTION_NAME_PATTERN = Pattern.compile( "([^\\d]+)?(?:(?<=^)|(?=$)|(?<=.):(?=.))(.+)?" );
private static final ConfigurationProperty<Boolean> SIGNING_ENABLED =
ConfigurationProperty.forKey( ElasticsearchAwsBackendSettings.SIGNING_ENABLED )
.asBoolean()
Expand Down Expand Up @@ -52,36 +55,48 @@ public class ElasticsearchAwsHttpClientConfigurer implements ElasticsearchHttpCl
.asString()
.build();

static final OptionalConfigurationProperty<String> DISTRIBUTION_NAME =
ConfigurationProperty.forKey( "version" )
.asString()
.build();

@Override
public void configure(ElasticsearchHttpClientConfigurationContext context) {
public Optional<ElasticsearchRequestInterceptor> provide(ElasticsearchRequestInterceptorProviderContext context) {
ConfigurationPropertySource propertySource = context.configurationPropertySource();

if ( !SIGNING_ENABLED.get( propertySource ) ) {
AwsLog.INSTANCE.signingDisabled();
return;
return Optional.empty();
}

Region region = REGION.getAndMapOrThrow( propertySource, Region::of, AwsLog.INSTANCE::missingPropertyForSigning );
String service;
switch ( context.configuredVersion().map( ElasticsearchVersion::distribution )
.orElse( ElasticsearchDistributionName.OPENSEARCH ) ) {
case AMAZON_OPENSEARCH_SERVERLESS:
service = "aoss";
break;
case ELASTIC:
case OPENSEARCH:
default:
service = "es";
break;

String distributionName = DISTRIBUTION_NAME.getAndTransform( propertySource,
v -> v.map( ver -> ver.toLowerCase( Locale.ROOT ) )
.map( DISTRIBUTION_NAME_PATTERN::matcher )
.map( matcher -> {
if ( matcher.matches() ) {
return matcher.group( 1 );
}
return null;
} ).orElse( "opensearch" ) );

if ( "amazon-opensearch-serverless".equals( distributionName ) ) {
service = "aoss";
}
else {
service = "es";
}

AwsCredentialsProvider credentialsProvider = createCredentialsProvider( context.beanResolver(), propertySource );

AwsLog.INSTANCE.signingEnabled( region, service, credentialsProvider );

AwsSigningRequestInterceptor signingInterceptor =
new AwsSigningRequestInterceptor( region, service, credentialsProvider );
ElasticsearchAwsSigningRequestInterceptor signingInterceptor =
new ElasticsearchAwsSigningRequestInterceptor( region, service, credentialsProvider );

context.clientBuilder().addInterceptorLast( signingInterceptor );
return Optional.of( signingInterceptor );
}

private AwsCredentialsProvider createCredentialsProvider(BeanResolver beanResolver,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.search.backend.elasticsearch.aws.impl;

import java.io.IOException;

import org.hibernate.search.backend.elasticsearch.aws.logging.impl.AwsLog;
import org.hibernate.search.backend.elasticsearch.client.common.spi.ElasticsearchRequestInterceptor;
import org.hibernate.search.backend.elasticsearch.client.common.spi.ElasticsearchRequestInterceptorContext;

import software.amazon.awssdk.auth.credentials.AwsCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.http.ContentStreamProvider;
import software.amazon.awssdk.http.SdkHttpFullRequest;
import software.amazon.awssdk.http.SdkHttpMethod;
import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner;
import software.amazon.awssdk.http.auth.spi.signer.SignedRequest;
import software.amazon.awssdk.regions.Region;

class ElasticsearchAwsSigningRequestInterceptor implements ElasticsearchRequestInterceptor {

private final AwsV4HttpSigner signer;
private final Region region;
private final String service;
private final AwsCredentialsProvider credentialsProvider;

ElasticsearchAwsSigningRequestInterceptor(Region region, String service, AwsCredentialsProvider credentialsProvider) {
this.signer = AwsV4HttpSigner.create();
this.region = region;
this.service = service;
this.credentialsProvider = credentialsProvider;
}

@Override
public void intercept(ElasticsearchRequestInterceptorContext requestContext) throws IOException {
try ( HttpEntityContentStreamProvider contentStreamProvider =
HttpEntityContentStreamProvider.create( requestContext ) ) {
sign( requestContext, contentStreamProvider );
}
}

private void sign(ElasticsearchRequestInterceptorContext requestContext,
HttpEntityContentStreamProvider contentStreamProvider) {
SdkHttpFullRequest awsRequest = toAwsRequest( requestContext, contentStreamProvider );

if ( AwsLog.INSTANCE.isTraceEnabled() ) {
AwsLog.INSTANCE.httpRequestBeforeSigning( requestContext );
AwsLog.INSTANCE.awsRequestBeforeSigning( awsRequest );
}

AwsCredentials credentials = credentialsProvider.resolveCredentials();
AwsLog.INSTANCE.awsCredentials( credentials );

SignedRequest signedRequest = signer.sign( r -> r.identity( credentials )
.request( awsRequest )
.payload( awsRequest.contentStreamProvider().orElse( null ) )
.putProperty( AwsV4HttpSigner.SERVICE_SIGNING_NAME, service )
.putProperty( AwsV4HttpSigner.REGION_NAME, region.id() ) );

// The AWS SDK added some headers.
// Let's just override the existing headers with whatever the AWS SDK came up with.
// We don't expect signing to affect anything else (path, query, content, ...).
requestContext.overrideHeaders( signedRequest.request().headers() );

if ( AwsLog.INSTANCE.isTraceEnabled() ) {
AwsLog.INSTANCE.httpRequestAfterSigning( signedRequest );
AwsLog.INSTANCE.awsRequestAfterSigning( requestContext );
}
}

private SdkHttpFullRequest toAwsRequest(
ElasticsearchRequestInterceptorContext requestContext,
ContentStreamProvider contentStreamProvider) {
SdkHttpFullRequest.Builder awsRequestBuilder = SdkHttpFullRequest.builder();

awsRequestBuilder.host( requestContext.host() );
awsRequestBuilder.port( requestContext.port() );
awsRequestBuilder.protocol( requestContext.scheme() );

awsRequestBuilder.method( SdkHttpMethod.fromValue( requestContext.method() ) );

String path = requestContext.path();

// For some reason this is needed on Amazon OpenSearch Serverless
if ( "aoss".equals( service ) ) {
awsRequestBuilder.appendHeader( "x-amz-content-sha256", "required" );
}

awsRequestBuilder.encodedPath( path );
for ( var param : requestContext.queryParameters().entrySet() ) {
awsRequestBuilder.appendRawQueryParameter( param.getKey(), param.getValue() );
}

// Do NOT copy the headers, as the AWS SDK will sometimes sign some headers
// that are not properly taken into account by the AWS servers (e.g. content-length).

awsRequestBuilder.contentStreamProvider( contentStreamProvider );

return awsRequestBuilder.build();
}

}
Loading