/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.elasticsearch.client.impl;
import java.util.Properties;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.nio.conn.NoopIOSessionStrategy;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.sniff.ElasticsearchHostsSniffer;
import org.elasticsearch.client.sniff.HostsSniffer;
import org.elasticsearch.client.sniff.Sniffer;
import org.elasticsearch.client.sniff.SnifferBuilder;
import org.hibernate.search.elasticsearch.cfg.ElasticsearchEnvironment;
import org.hibernate.search.elasticsearch.logging.impl.Log;
import org.hibernate.search.util.configuration.impl.ConfigurationParseHelper;
import org.hibernate.search.util.impl.SearchThreadFactory;
import org.hibernate.search.util.logging.impl.LoggerFactory;
/**
* @author Gunnar Morling
* @author Yoann Rodiere
*/
public class DefaultElasticsearchClientFactory implements ElasticsearchClientFactory {
private static final Log log = LoggerFactory.make( Log.class );
private static final String HTTP_SCHEME = "http";
/**
* Prefix for accessing the client-related settings.
* To be suffixed by a scope (currently always "default")
* and the name of the property to access.
*/
private static final String CLIENT_PROP_PREFIX = "hibernate.search.";
@Override
public ElasticsearchClientImplementor create(String scopeName, Properties properties) {
RestClient restClient = createClient( scopeName, properties );
Sniffer sniffer = createSniffer( scopeName, restClient, properties );
return new DefaultElasticsearchClient( restClient, sniffer );
}
private RestClient createClient(String scopeName, Properties properties) {
String propertyPrefix = propertyPrefix( scopeName );
String serverUrisString = ConfigurationParseHelper.getString(
properties,
propertyPrefix + ElasticsearchEnvironment.SERVER_URI,
ElasticsearchEnvironment.Defaults.SERVER_URI
);
ServerUris hosts = ServerUris.fromString( serverUrisString );
return RestClient.builder( hosts.asHostsArray() )
/*
* Note: this timeout is not only used on retries,
* but also when executing requests synchronously.
* See https://github.com/elastic/elasticsearch/issues/21789#issuecomment-287399115
*/
.setMaxRetryTimeoutMillis( ConfigurationParseHelper.getIntValue(
properties,
propertyPrefix + ElasticsearchEnvironment.SERVER_REQUEST_TIMEOUT,
ElasticsearchEnvironment.Defaults.SERVER_REQUEST_TIMEOUT
) )
.setRequestConfigCallback( (b) -> customizeRequestConfig( propertyPrefix, properties, b ) )
.setHttpClientConfigCallback( (b) -> customizeHttpClientConfig( propertyPrefix, properties, hosts, b ) )
.build();
}
private Sniffer createSniffer(String scopeName, RestClient client, Properties properties) {
String propertyPrefix = propertyPrefix( scopeName );
boolean discoveryEnabled = ConfigurationParseHelper.getBooleanValue(
properties,
propertyPrefix + ElasticsearchEnvironment.DISCOVERY_ENABLED,
ElasticsearchEnvironment.Defaults.DISCOVERY_ENABLED
);
if ( discoveryEnabled ) {
SnifferBuilder builder = Sniffer.builder( client )
.setSniffIntervalMillis(
ConfigurationParseHelper.getIntValue(
properties,
propertyPrefix + ElasticsearchEnvironment.DISCOVERY_REFRESH_INTERVAL,
ElasticsearchEnvironment.Defaults.DISCOVERY_REFRESH_INTERVAL
)
* 1_000 // The configured value is in seconds
);
String scheme = ConfigurationParseHelper.getString(properties, propertyPrefix + ElasticsearchEnvironment.DISCOVERY_SCHEME, "http");
// https discovery support
if ( scheme.equals(ElasticsearchHostsSniffer.Scheme.HTTPS.toString()) ) {
HostsSniffer hostsSniffer = new ElasticsearchHostsSniffer(
client,
ElasticsearchHostsSniffer.DEFAULT_SNIFF_REQUEST_TIMEOUT, // 1sec
ElasticsearchHostsSniffer.Scheme.HTTPS );
builder.setHostsSniffer( hostsSniffer );
}
return builder.build();
}
else {
return null;
}
}
private HttpAsyncClientBuilder customizeHttpClientConfig(String propertyPrefix,
Properties properties, ServerUris hosts, HttpAsyncClientBuilder builder) {
builder = builder
.setMaxConnTotal( ConfigurationParseHelper.getIntValue(
properties,
propertyPrefix + ElasticsearchEnvironment.MAX_TOTAL_CONNECTION,
ElasticsearchEnvironment.Defaults.MAX_TOTAL_CONNECTION
) )
.setMaxConnPerRoute( ConfigurationParseHelper.getIntValue(
properties,
propertyPrefix + ElasticsearchEnvironment.MAX_TOTAL_CONNECTION_PER_ROUTE,
ElasticsearchEnvironment.Defaults.MAX_TOTAL_CONNECTION_PER_ROUTE
) )
.setThreadFactory( new SearchThreadFactory( "Elasticsearch transport thread" ) );
if ( hosts.isAnyRequiringSSL() == false ) {
// In this case disable the SSL capability as it might have an impact on
// bootstrap time, for example consuming entropy for no reason
builder.setSSLStrategy( NoopIOSessionStrategy.INSTANCE );
}
String username = ConfigurationParseHelper.getString(
properties,
propertyPrefix + ElasticsearchEnvironment.SERVER_USERNAME,
null
);
if ( username != null ) {
String password = ConfigurationParseHelper.getString(
properties,
propertyPrefix + ElasticsearchEnvironment.SERVER_PASSWORD,
null
);
if ( password != null ) {
hosts.warnPasswordsOverHttp();
}
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(
new AuthScope( AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM, AuthScope.ANY_SCHEME ),
new UsernamePasswordCredentials( username, password )
);
builder = builder.setDefaultCredentialsProvider( credentialsProvider );
}
return builder;
}
private RequestConfig.Builder customizeRequestConfig(String propertyPrefix, Properties properties, RequestConfig.Builder builder) {
return builder
.setConnectionRequestTimeout( 0 ) //Disable lease handling for the connection pool! See also HSEARCH-2681
.setSocketTimeout( ConfigurationParseHelper.getIntValue(
properties,
propertyPrefix + ElasticsearchEnvironment.SERVER_READ_TIMEOUT,
ElasticsearchEnvironment.Defaults.SERVER_READ_TIMEOUT
) )
.setConnectTimeout( ConfigurationParseHelper.getIntValue(
properties,
propertyPrefix + ElasticsearchEnvironment.SERVER_CONNECTION_TIMEOUT,
ElasticsearchEnvironment.Defaults.SERVER_CONNECTION_TIMEOUT
) );
}
private String propertyPrefix(String scopeName) {
return new StringBuilder( CLIENT_PROP_PREFIX )
.append( scopeName ).append( "." )
.toString();
}
}