All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hibernate.search.elasticsearch.client.impl.JestClient Maven / Gradle / Ivy

/*
 * 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 .
 */
package org.hibernate.search.elasticsearch.client.impl;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import org.hibernate.search.elasticsearch.cfg.ElasticsearchEnvironment;
import org.hibernate.search.elasticsearch.impl.GsonService;
import org.hibernate.search.elasticsearch.logging.impl.Log;
import org.hibernate.search.engine.service.spi.Service;
import org.hibernate.search.engine.service.spi.ServiceManager;
import org.hibernate.search.engine.service.spi.Startable;
import org.hibernate.search.engine.service.spi.Stoppable;
import org.hibernate.search.spi.BuildContext;
import org.hibernate.search.util.configuration.impl.ConfigurationParseHelper;
import org.hibernate.search.util.logging.impl.LoggerFactory;

import com.google.gson.JsonElement;

import io.searchbox.action.Action;
import io.searchbox.action.BulkableAction;
import io.searchbox.action.DocumentTargetedAction;
import io.searchbox.client.JestClientFactory;
import io.searchbox.client.JestResult;
import io.searchbox.client.config.HttpClientConfig;
import io.searchbox.core.Bulk;
import io.searchbox.core.Bulk.Builder;
import io.searchbox.core.BulkResult;
import io.searchbox.core.BulkResult.BulkResultItem;
import io.searchbox.params.Parameters;

/**
 * Provides access to the JEST client.
 *
 * @author Gunnar Morling
 */
public class JestClient implements Service, Startable, Stoppable {

	private static final Log LOG = LoggerFactory.make( Log.class );

	/**
	 * HTTP response code for a request timed out.
	 */
	private static final int TIME_OUT = 408;

	/**
	 * Prefix for accessing the {@link ElasticsearchEnvironment#SERVER_URI} variable. That's currently needed as we
	 * don't access this one in the context of a specific index manager. The prefix is used to have the property name
	 * in line with the other index-related property names, even though this property can not yet be override on
	 * a per-index base.
	 */
	private static final String SERVER_URI_PROP_PREFIX = "hibernate.search.default.";

	private io.searchbox.client.JestClient client;

	private ServiceManager serviceManager;
	private GsonService gsonService;

	@Override
	public void start(Properties properties, BuildContext context) {
		serviceManager = context.getServiceManager();
		gsonService = serviceManager.requestService( GsonService.class );

		JestClientFactory factory = new JestClientFactory();

		String serverUri = ConfigurationParseHelper.getString(
				properties,
				SERVER_URI_PROP_PREFIX + ElasticsearchEnvironment.SERVER_URI,
				ElasticsearchEnvironment.Defaults.SERVER_URI
		);

		// TODO HSEARCH-2062 Make timeouts configurable
		factory.setHttpClientConfig(
			new HttpClientConfig.Builder( serverUri )
				.multiThreaded( true )
				.readTimeout( 60000 )
				.connTimeout( 2000 )
				.gson( gsonService.getGson() )
				.build()
		);

		client = factory.getObject();
	}

	@Override
	public void stop() {
		client.shutdownClient();
		client = null;

		gsonService = null;
		serviceManager.releaseService( GsonService.class );
		serviceManager = null;
	}

	public  T executeRequest(Action request, int... ignoredErrorStatuses) {
		return executeRequest( request, asSet( ignoredErrorStatuses ) );
	}

	public  T executeRequest(Action request, Set ignoredErrorStatuses) {
		try {
			T result = client.execute( request );

			// The request failed with a status that's not ignore-able
			if ( !result.isSucceeded() && !isIgnored( result.getResponseCode(), ignoredErrorStatuses ) ) {
				if ( result.getResponseCode() == TIME_OUT ) {
					throw LOG.elasticsearchRequestTimeout( requestToString( request ), resultToString( result ) );
				}
				else {
					throw LOG.elasticsearchRequestFailed( requestToString( request ), resultToString( result ), null );
				}
			}

			return result;
		}
		catch (IOException e) {
			throw LOG.elasticsearchRequestFailed( requestToString( request ), null, e );
		}
	}

	/**
	 * Creates a bulk action from the given list and executes it.
	 */
	public void executeBulkRequest(List> actions, boolean refresh) {
		Builder bulkBuilder = new Bulk.Builder()
				.setParameter( Parameters.REFRESH, refresh );

		for ( BackendRequest action : actions ) {
			bulkBuilder.addAction( (BulkableAction) action.getAction() );
		}

		Bulk request = bulkBuilder.build();

		try {
			BulkResult response = client.execute( request );

			// Ideally I could just check on the status of the bulk, but for some reason the ES response is not
			// always set to erroneous also if there is an erroneous item; I suppose that's a bug in ES? For now we are
			// examining the result items and check if there is any erroneous
			List> erroneousItems = getErroneousItems( actions, response );

			if ( !erroneousItems.isEmpty() ) {
				throw LOG.elasticsearchBulkRequestFailed(
						requestToString( request ),
						resultToString( response ),
						erroneousItems
				);
			}
		}
		catch (IOException e) {
			throw LOG.elasticsearchRequestFailed( requestToString( request ), null, e );
		}
	}

	private List> getErroneousItems(List> actions, BulkResult response) {
		int i = 0;

		List> erroneousItems = new ArrayList<>();

		for ( BulkResultItem resultItem : response.getItems() ) {
			// When getting a 404 for a DELETE, the error is null :(, so checking both
			if ( resultItem.error != null || resultItem.status >= 400 ) {
				BackendRequest action = actions.get( i );
				if ( !action.getIgnoredErrorStatuses().contains( resultItem.status ) ) {
					erroneousItems.add( action );
				}
			}
			i++;
		}

		return erroneousItems;
	}

	private boolean isIgnored(int responseCode, Set ignoredStatuses) {
		if ( ignoredStatuses == null ) {
			return true;
		}
		else {
			return ignoredStatuses.contains( responseCode );
		}
	}

	private Set asSet(int... ignoredErrorStatuses) {
		Set ignored;

		if ( ignoredErrorStatuses == null || ignoredErrorStatuses.length == 0 ) {
			ignored = Collections.emptySet();
		}
		else if ( ignoredErrorStatuses.length == 1 ) {
			ignored = Collections.singleton( ignoredErrorStatuses[0] );
		}
		else {
			ignored = new HashSet<>();
			for ( int ignoredErrorStatus : ignoredErrorStatuses ) {
				ignored.add( ignoredErrorStatus );
			}
		}

		return ignored;
	}

	private String requestToString(Action action) {
		StringBuilder sb = new StringBuilder();

		sb.append( "Operation: " ).append( action.getClass().getSimpleName() ).append( "\n" );

		if ( action instanceof DocumentTargetedAction ) {
			sb.append( "Index: " ).append( ( (DocumentTargetedAction) action ).getIndex() ).append( "\n" );
			sb.append( "Type: " ).append( ( (DocumentTargetedAction) action ).getType() ).append( "\n" );
			sb.append( "Id: " ).append( ( (DocumentTargetedAction) action ).getId() ).append( "\n" );
		}

		sb.append( "Data:\n" );
		sb.append( action.getData( gsonService.getGson() ) );
		sb.append( "\n" );
		return sb.toString();
	}

	private String resultToString(JestResult result) {
		StringBuilder sb = new StringBuilder();
		sb.append( "Status: " ).append( result.getResponseCode() ).append( "\n" );
		sb.append( "Error message: " ).append( result.getErrorMessage() ).append( "\n" );
		sb.append( "Cluster name: " ).append( property( result, "cluster_name" ) ).append( "\n" );
		sb.append( "Cluster status: " ).append( property( result, "status" ) ).append( "\n" );
		sb.append( "\n" );

		if ( result instanceof BulkResult ) {
			for ( BulkResultItem item : ( (BulkResult) result ).getItems() ) {
				sb.append( "Operation: " ).append( item.operation ).append( "\n" );
				sb.append( "  Index: " ).append( item.index ).append( "\n" );
				sb.append( "  Type: " ).append( item.type ).append( "\n" );
				sb.append( "  Id: " ).append( item.id ).append( "\n" );
				sb.append( "  Status: " ).append( item.status ).append( "\n" );
				sb.append( "  Error: " ).append( item.error ).append( "\n" );
			}
		}

		return sb.toString();
	}

	private String property(JestResult result, String name) {
		if ( result.getJsonObject() == null ) {
			return null;
		}
		JsonElement clusterName = result.getJsonObject().get( name );
		if ( clusterName == null ) {
			return null;
		}
		return clusterName.getAsString();
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy