org.hibernate.search.elasticsearch.client.impl.JestClient.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.search.elasticsearch.client.impl.JestClient.java

Source

/*
 * 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.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
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.impl.CollectionHelper;
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 serverUrisString = ConfigurationParseHelper.getString(properties,
                SERVER_URI_PROP_PREFIX + ElasticsearchEnvironment.SERVER_URI,
                ElasticsearchEnvironment.Defaults.SERVER_URI);

        Collection<String> serverUris = Arrays.asList(serverUrisString.trim().split("\\s"));

        // TODO HSEARCH-2062 Make timeouts configurable
        factory.setHttpClientConfig(new HttpClientConfig.Builder(serverUris).multiThreaded(true)
                .readTimeout(ElasticsearchEnvironment.Defaults.SERVER_READ_TIMEOUT)
                .connTimeout(ElasticsearchEnvironment.Defaults.SERVER_CONNECTION_TIMEOUT)
                .gson(gsonService.getGson()).build());

        client = factory.getObject();
    }

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

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

    /**
     * Just to remove ambiguity between {@link #executeRequest(Action, int...)} and {@link #executeRequest(Action, String...)}
     * when the vararg is empty.
     */
    public <T extends JestResult> T executeRequest(Action<T> request) {
        return executeRequest(request, Collections.<Integer>emptySet(), Collections.<String>emptySet());
    }

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

    public <T extends JestResult> T executeRequest(Action<T> request, String... ignoredErrorTypes) {
        return executeRequest(request, Collections.<Integer>emptySet(),
                CollectionHelper.asImmutableSet(ignoredErrorTypes));
    }

    public <T extends JestResult> T executeRequest(Action<T> request, Set<Integer> ignoredErrorStatuses) {
        return executeRequest(request, ignoredErrorStatuses, Collections.<String>emptySet());
    }

    public <T extends JestResult> T executeRequest(Action<T> request, Set<Integer> ignoredErrorStatuses,
            Set<String> ignoredErrorTypes) {
        try {
            T result = client.execute(request);

            // The request failed with a status that's not ignore-able
            if (!result.isSucceeded() && !isResponseCode(result.getResponseCode(), ignoredErrorStatuses)
                    && !isErrorType(result, ignoredErrorTypes)) {
                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 Map<BackendRequest<?>, BulkResultItem> executeBulkRequest(List<BackendRequest<?>> backendRequests,
            boolean refresh) {
        Builder bulkBuilder = new Bulk.Builder().setParameter(Parameters.REFRESH, refresh);

        for (BackendRequest<?> backendRequest : backendRequests) {
            bulkBuilder.addAction((BulkableAction<?>) backendRequest.getAction());
        }

        Bulk request = bulkBuilder.build();

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

            Map<BackendRequest<?>, BulkResultItem> successfulItems = CollectionHelper
                    .newHashMap(backendRequests.size());

            /*
             * We can't rely on the status of the bulk, since each backend request may consider specific
             * status codes as a success regardless of their usual meaning, which Elasticsearch doesn't
             * know about when computing the status of the bulk.
             */
            List<BackendRequest<?>> erroneousItems = new ArrayList<>();
            int i = 0;
            for (BulkResultItem resultItem : response.getItems()) {
                BackendRequest<?> backendRequest = backendRequests.get(i);

                if (isErrored(backendRequest, resultItem)) {
                    erroneousItems.add(backendRequest);
                } else {
                    successfulItems.put(backendRequest, resultItem);
                }
                ++i;
            }

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

    private boolean isErrored(BackendRequest<?> backendRequest, BulkResultItem resultItem) {
        // When getting a 404 for a DELETE, the error is null :(, so checking both
        return (resultItem.error != null || resultItem.status >= 400)
                && !isResponseCode(resultItem.status, backendRequest.getIgnoredErrorStatuses());
    }

    private boolean isResponseCode(int responseCode, Set<Integer> codes) {
        if (codes == null) {
            return false;
        } else {
            return codes.contains(responseCode);
        }
    }

    private boolean isErrorType(JestResult result, Set<String> errorTypes) {
        if (errorTypes == null) {
            return false;
        } else {
            return errorTypes.contains(getErrorType(result));
        }
    }

    private String getErrorType(JestResult result) {
        JsonElement error = result.getJsonObject().get("error");
        if (error == null || !error.isJsonObject()) {
            return null;
        }

        JsonElement errorType = error.getAsJsonObject().get("type");
        if (errorType == null || !errorType.isJsonPrimitive()) {
            return null;
        }

        return errorType.getAsString();
    }

    private Set<Integer> asSet(int... ignoredErrorStatuses) {
        Set<Integer> 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");
        sb.append("URI:").append(action.getURI()).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();
    }
}