package org.sef4j.elasticsearch; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Collection; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.function.Function; import org.elasticsearch.action.ActionFuture; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.client.Client; import org.sef4j.core.api.EventSender; import org.sef4j.core.helpers.senders.BulkAsyncSender; import org.sef4j.core.util.IPropertyChangeListenerSupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Facade for ElasticSearch client API */ public class ElasticSearchClientFacade { static final Logger LOG = LoggerFactory.getLogger(ElasticSearchClientFacade.class); private String displayName; private Callable<Client> esClientProvider; private Client esClient; protected PropertyChangeListener innerPropChangeListener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { onEsClientProviderPropertyChange(evt); } }; /** low-level adapter to EventSender<ActionRequest>, to send requests to underlying esClient */ private InnerBulkActionRequestEventSender esClientBulkActionRequestEventSender = new InnerBulkActionRequestEventSender(); // private ElasticSearchBulkAsyncDisrupterHelper asyncDisrupterHelper; private BulkAsyncSender<ActionRequest<?>> bulkActionRequestAsyncSender; private static final Function<ActionRequest<?>,Integer> asyncRequestToByteLengthProvider = new Function<ActionRequest<?>,Integer>() { @Override public Integer apply(ActionRequest<?> t) { BulkRequest bulk = new BulkRequest(); bulk.add(t); return (int) bulk.estimatedSizeInBytes(); // return ((IndexRequest)t).source().length() + 50; // cf BulkRequest.estimatedSizeInBytes() BulkRequest.REQUEST_OVERHEAD } }; // ------------------------------------------------------------------------ public ElasticSearchClientFacade(String displayName, Callable<Client> esClientFactory, BulkAsyncSender.Builder<ActionRequest<?>> asyncSettingsBuilder) { this.displayName = displayName; this.esClientProvider = esClientFactory; if (esClientProvider instanceof IPropertyChangeListenerSupport) { IPropertyChangeListenerSupport listenerSupport = (IPropertyChangeListenerSupport) esClientProvider; listenerSupport.addPropertyChangeListener(innerPropChangeListener); } if (asyncSettingsBuilder == null) { asyncSettingsBuilder = new BulkAsyncSender.Builder<ActionRequest<?>>();// use default } asyncSettingsBuilder.eventByteLengthProvider(asyncRequestToByteLengthProvider); if (asyncSettingsBuilder.getAsyncDisruptorErrorHandler() == null) { asyncSettingsBuilder.asyncDisruptorErrorHandler(new DefaultAsyncDisrupterErrorHandler<ActionRequest<?>>()); } this.bulkActionRequestAsyncSender = asyncSettingsBuilder.build(esClientBulkActionRequestEventSender); } // support for ES Client init / dispose and support for update from Client Provider // ------------------------------------------------------------------------ public void start() { Client cli = esClient; if (cli != null) { return; } try { this.esClient = esClientProvider.call(); } catch (Exception ex) { throw new RuntimeException("Failed to create Elasticsearch client!", ex); } bulkActionRequestAsyncSender.start(); } public void stop() { Client toClose = esClient; if (toClose != null) { this.esClient = null; try { toClose.close(); } catch(Exception ex) { LOG.warn("Failed to close esClient, ex=" + ex.getMessage() + " ... ignore, no rethrow!"); } } bulkActionRequestAsyncSender.stop(); } protected void onEsClientProviderPropertyChange(PropertyChangeEvent evt) { Client toClose = esClient; if (toClose != null) { stop(); start(); } } // delegate to esClient // ------------------------------------------------------------------------ public Client getEsClient() { // TOADD ... may return a wrapper, to increment counter stats return esClient; } public void bulk(BulkRequest request, ActionListener<BulkResponse> listener) { esClient.bulk(request, listener); } public ActionFuture<BulkResponse> bulk(BulkRequest request) { return esClient.bulk(request); } // public ActionFuture<BulkResponse> bulkIndex(IndexRequest... indexRequests) { // BulkRequest bulkReq = new BulkRequest(); // for (IndexRequest r : indexRequests) { // bulkReq.add(r); // } // return bulk(bulkReq); // } // // public static void bulkAddIndex(BulkRequest bulkRequest, // String index, String type, // Object... source) { // IndexRequest req = new IndexRequest(index, type).source(source); // bulkRequest.add(req); // } // Support for asynchronous buffering + and periodic flushing // ------------------------------------------------------------------------ public void asyncRequest(ActionRequest<?> req) { bulkActionRequestAsyncSender.sendEvent(req); } public void asyncRequests(Collection<ActionRequest<?>> reqs) { bulkActionRequestAsyncSender.sendEvents(reqs); } public void asyncIndexRequest(String index, String type, Object... source) { IndexRequest req = new IndexRequest(index, type).source(source); bulkActionRequestAsyncSender.sendEvent(req); } /** * Internal adapter to send ElasticSearch ActionRequest (or grouped by BulkRequest), using the EventSender API * */ private class InnerBulkActionRequestEventSender implements EventSender<ActionRequest<?>> { @Override public void sendEvent(ActionRequest<?> event) { // send 1 ActionRequest : using bulk? BulkRequest bulkReq = new BulkRequest(); bulkReq.add(event); ActionFuture<BulkResponse> resp = bulk(bulkReq); waitAndRethrowEx(resp); } @Override public void sendEvents(Collection<ActionRequest<?>> events) { BulkRequest bulkReq = new BulkRequest(); for (ActionRequest<?> r : events) { bulkReq.add(r); } ActionFuture<BulkResponse> resp = bulk(bulkReq); waitAndRethrowEx(resp); } private void waitAndRethrowEx(ActionFuture<?> resp) { // force get synchronous result to throws exception on error in caller thread! try { resp.get(); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (ExecutionException e) { throw new RuntimeException(e); } } } // ------------------------------------------------------------------------ @Override public String toString() { return "ElasticSearchClient[" + displayName + "]"; } }