<p dir="auto">I'm running version 0.19.9</p>
<p dir="auto">curl -XGET '<a href="http://xxx:9200/_cluster/health?pretty=true" rel="nofollow">http://xxx:9200/_cluster/health?pretty=true</a>'<br>
{<br>
"cluster_name" : "elasticsearch",<br>
"status" : "yellow",<br>
"timed_out" : false,<br>
"number_of_nodes" : 2,<br>
"number_of_data_nodes" : 1,<br>
"active_primary_shards" : 85,<br>
"active_shards" : 85,<br>
"relocating_shards" : 0,<br>
"initializing_shards" : 0,<br>
"unassigned_shards" : 85<br>
}</p>
<p dir="auto">This works:<br>
curl -XPUT xxx:9200/test/test/1 -d '{"counter" : 1, "tags" : ["fubar1"]}'<br>
curl -XPOST 'xxx:9200/test/test/1/_update' -d '{"script" : "ctx._source.tags += tag","params": {"tag":"fubar2"}}'</p>
<p dir="auto">This does not work:<br>
curl -XPUT xxx:9200/test/test/2 -d '{"counter" : 1, "<a class="user-mention notranslate" data-hovercard-type="organization" data-hovercard-url="/orgs/tags/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/tags">@tags</a>" : ["fubar1"]}'<br>
curl -XPOST 'xxx:9200/test/test/2/_update' -d '{"script" : "ctx._source.<a class="user-mention notranslate" data-hovercard-type="organization" data-hovercard-url="/orgs/tags/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/tags">@tags</a> += tag","params": {"tag":"fubar2"}}'</p>
<p dir="auto">{"error":"ElasticSearchIllegalArgumentException[failed to execute script]; nested: ArrayIndexOutOfBoundsException[0]; ","status":400}</p>
<p dir="auto">Posted in mailing list:<br>
<a href="https://groups.google.com/d/topic/elasticsearch/SZgbpMj00lk/discussion" rel="nofollow">https://groups.google.com/d/topic/elasticsearch/SZgbpMj00lk/discussion</a></p>
<h3 dir="auto">Background / High Level Goal</h3>
<p dir="auto">While working on <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="115852085" data-permission-text="Title is private" data-url="https://github.com/elastic/elasticsearch/issues/14620" data-hovercard-type="issue" data-hovercard-url="/elastic/elasticsearch/issues/14620/hovercard" href="https://github.com/elastic/elasticsearch/issues/14620">#14620</a> I noticed that the current listener-based client API does not compose, i.e. I cannot build processing pipelines based on a response future.</p>
<p dir="auto">For example, in the Java client implementation classes, I want to write code like:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="// 'X' is some not yet specified interface potentially extending ActionFuture 
// for backwards compatibility
public X&lt;BulkRequest&gt; bulk(BulkRequest request) {
   return Retry.withBackoff(() -&gt; client.bulk(request))
     // not important here what #doCleanup does exactly
    .onCompletion((response) -&gt; doCleanup()) 
    .onException((ex) -&gt; doCleanup());
}"><pre class="notranslate"><code class="notranslate">// 'X' is some not yet specified interface potentially extending ActionFuture 
// for backwards compatibility
public X&lt;BulkRequest&gt; bulk(BulkRequest request) {
   return Retry.withBackoff(() -&gt; client.bulk(request))
     // not important here what #doCleanup does exactly
    .onCompletion((response) -&gt; doCleanup()) 
    .onException((ex) -&gt; doCleanup());
}
</code></pre></div>
<p dir="auto">And I want to do all that without knowing what any potential caller of the Java client API is doing. The caller should just be able to add its operations on top, i.e. we want to build a pipeline of operations, e.g.:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="client()
  .bulk(request)
  .onCompletion((response) -&gt; updateUI())
  .onException((ex) -&gt; showErrorMessage(ex));"><pre class="notranslate"><code class="notranslate">client()
  .bulk(request)
  .onCompletion((response) -&gt; updateUI())
  .onException((ex) -&gt; showErrorMessage(ex));
</code></pre></div>
<h3 dir="auto">Current Status</h3>
<p dir="auto">Chaining of listeners is currently also doable but listeners need to delegate explicitly and the code gets very tightly coupled and ugly.</p>
<h3 dir="auto">Possible Solution Approach</h3>
<p dir="auto">Starting with Java 8, the JDK provides <code class="notranslate">CompletableFuture</code> which allows to create processing pipelines. However, the <code class="notranslate">CompletableFuture</code> API is very hard to understand and I doubt we need its whole complexity for our use case.</p>
<p dir="auto">Nevertheless, it could be worthwhile to base the internal implementation on <code class="notranslate">CompletableFuture</code> and just expose parts the API.</p>
<h3 dir="auto">Necessary Steps</h3>
<ol dir="auto">
<li>Start investigating whether <code class="notranslate">CompletableFuture</code> is a good fit for our requirements and flesh out an updated client API (ideally without breaking backwards compatibility, even on master)</li>
<li>Deprecate <code class="notranslate">ActionListener</code> and all methods in the Java client API that take one as parameter (to be removed with next major version)</li>
<li>Add new methods to <code class="notranslate">ActionFuture</code> to allow for chaining. This way, the current API methods that return an <code class="notranslate">ActionFuture</code> will stay but we can get rid of all <code class="notranslate">ActionListener</code> methods. Deprecated methods based on <code class="notranslate">ActionListener</code> can be simply reimplemented internally on top of the new API.</li>
<li>Revisit implementations of ActionFuture (whether we still need them at all) and provide a new default implementation that delegates to <code class="notranslate">CompletableFuture</code></li>
<li>Update the client documentation with hints on how to migrate the API</li>
<li>(As separate PRs) Adapt internal implementation to take advantage of pipelining.</li>
</ol>