Browse Source

Add Get Source API to the HLRC (#50885)

Relates to #47678
Maxim 5 years ago
parent
commit
c01923043d

+ 20 - 3
client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java

@@ -49,6 +49,7 @@ import org.elasticsearch.action.support.IndicesOptions;
 import org.elasticsearch.action.support.WriteRequest;
 import org.elasticsearch.action.update.UpdateRequest;
 import org.elasticsearch.client.core.CountRequest;
+import org.elasticsearch.client.core.GetSourceRequest;
 import org.elasticsearch.client.core.MultiTermVectorsRequest;
 import org.elasticsearch.client.core.TermVectorsRequest;
 import org.elasticsearch.client.indices.AnalyzeRequest;
@@ -275,14 +276,30 @@ final class RequestConverters {
     }
 
     static Request sourceExists(GetRequest getRequest) {
-        String endpoint = endpoint(getRequest.index(), "_source", getRequest.id());
-        Request request = new Request(HttpHead.METHOD_NAME, endpoint);
         Params parameters = new Params();
         parameters.withPreference(getRequest.preference());
         parameters.withRouting(getRequest.routing());
         parameters.withRefresh(getRequest.refresh());
         parameters.withRealtime(getRequest.realtime());
-        // Version params are not currently supported by the source exists API so are not passed
+        parameters.withFetchSourceContext(getRequest.fetchSourceContext());
+        // Version params are not currently supported by the _source API so are not passed
+
+        String endpoint = endpoint(getRequest.index(), "_source", getRequest.id());
+        Request request = new Request(HttpHead.METHOD_NAME, endpoint);
+        request.addParameters(parameters.asMap());
+        return request;
+    }
+
+    static Request getSource(GetSourceRequest getSourceRequest) {
+        Params parameters = new Params();
+        parameters.withPreference(getSourceRequest.preference());
+        parameters.withRouting(getSourceRequest.routing());
+        parameters.withRefresh(getSourceRequest.refresh());
+        parameters.withRealtime(getSourceRequest.realtime());
+        parameters.withFetchSourceContext(getSourceRequest.fetchSourceContext());
+
+        String endpoint = endpoint(getSourceRequest.index(), "_source", getSourceRequest.id());
+        Request request = new Request(HttpGet.METHOD_NAME, endpoint);
         request.addParameters(parameters.asMap());
         return request;
     }

+ 30 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java

@@ -56,6 +56,8 @@ import org.elasticsearch.action.update.UpdateRequest;
 import org.elasticsearch.action.update.UpdateResponse;
 import org.elasticsearch.client.core.CountRequest;
 import org.elasticsearch.client.core.CountResponse;
+import org.elasticsearch.client.core.GetSourceRequest;
+import org.elasticsearch.client.core.GetSourceResponse;
 import org.elasticsearch.client.core.MainRequest;
 import org.elasticsearch.client.core.MainResponse;
 import org.elasticsearch.client.core.MultiTermVectorsRequest;
@@ -860,6 +862,34 @@ public class RestHighLevelClient implements Closeable {
             RestHighLevelClient::convertExistsResponse, listener, emptySet());
     }
 
+    /**
+     * Retrieves the source field only of a document using GetSource API.
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-get.html#_source">Get Source API
+     * on elastic.co</a>
+     * @param getRequest the request
+     * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @return the response
+     */
+    public GetSourceResponse getSource(GetSourceRequest getRequest, RequestOptions options) throws IOException {
+        return performRequestAndParseEntity(getRequest, RequestConverters::getSource, options,
+            GetSourceResponse::fromXContent, emptySet());
+    }
+
+    /**
+     * Asynchronously retrieves the source field only of a document using GetSource API.
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-get.html#_source">Get Source API
+     * on elastic.co</a>
+     * @param getRequest the request
+     * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @param listener the listener to be notified upon request completion
+     * @return cancellable that may be used to cancel the request
+     */
+    public final Cancellable getSourceAsync(GetSourceRequest getRequest, RequestOptions options,
+                                            ActionListener<GetSourceResponse> listener) {
+        return performRequestAsyncAndParseEntity(getRequest, RequestConverters::getSource, options,
+            GetSourceResponse::fromXContent, listener, emptySet());
+    }
+
     /**
      * Index a document using the Index API.
      * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html">Index API on elastic.co</a>

+ 126 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/core/GetSourceRequest.java

@@ -0,0 +1,126 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.core;
+
+import org.elasticsearch.client.Validatable;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
+
+import java.io.IOException;
+
+public final class GetSourceRequest implements Validatable, ToXContentObject {
+    private String routing;
+    private String preference;
+
+    private boolean refresh = false;
+    private boolean realtime = true;
+
+    private FetchSourceContext fetchSourceContext;
+
+    private String index;
+    private String id;
+
+    public GetSourceRequest(String index, String id) {
+        this.index = index;
+        this.id = id;
+    }
+
+    /**
+     * Controls the shard routing of the request. Using this value to hash the shard
+     * and not the id.
+     */
+    public GetSourceRequest routing(String routing) {
+        if (routing != null && routing.length() == 0) {
+            this.routing = null;
+        } else {
+            this.routing = routing;
+        }
+        return this;
+    }
+
+    /**
+     * Sets the preference to execute the search. Defaults to randomize across shards. Can be set to
+     * {@code _local} to prefer local shards or a custom value, which guarantees that the same order
+     * will be used across different requests.
+     */
+    public GetSourceRequest preference(String preference) {
+        this.preference = preference;
+        return this;
+    }
+
+    /**
+     * Should a refresh be executed before this get operation causing the operation to
+     * return the latest value. Note, heavy get should not set this to {@code true}. Defaults
+     * to {@code false}.
+     */
+    public GetSourceRequest refresh(boolean refresh) {
+        this.refresh = refresh;
+        return this;
+    }
+
+    public GetSourceRequest realtime(boolean realtime) {
+        this.realtime = realtime;
+        return this;
+    }
+
+    /**
+     * Allows setting the {@link FetchSourceContext} for this request, controlling if and how _source should be returned.
+     * Note, the {@code fetchSource} field of the context must be set to {@code true}.
+     */
+
+    public GetSourceRequest fetchSourceContext(FetchSourceContext context) {
+        this.fetchSourceContext = context;
+        return this;
+    }
+
+    @Override
+    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+        return null;
+    }
+
+    public String index() {
+        return index;
+    }
+
+    public String id() {
+        return id;
+    }
+
+    public String routing() {
+        return routing;
+    }
+
+    public String preference() {
+        return preference;
+    }
+
+    public boolean refresh() {
+        return refresh;
+    }
+
+    public boolean realtime() {
+        return realtime;
+    }
+
+    public FetchSourceContext fetchSourceContext() {
+        return fetchSourceContext;
+    }
+}

+ 47 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/core/GetSourceResponse.java

@@ -0,0 +1,47 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.core;
+
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+import java.util.Map;
+
+public final class GetSourceResponse {
+
+    private final Map<String, Object> source;
+
+    public GetSourceResponse(Map<String, Object> source) {
+        this.source = source;
+    }
+
+    public static GetSourceResponse fromXContent(XContentParser parser) throws IOException {
+        return new GetSourceResponse(parser.map());
+    }
+
+    public Map<String, Object> getSource() {
+        return this.source;
+    }
+
+    @Override
+    public String toString() {
+        return source.toString();
+    }
+}

+ 68 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/CrudIT.java

@@ -38,6 +38,8 @@ import org.elasticsearch.action.index.IndexResponse;
 import org.elasticsearch.action.support.WriteRequest.RefreshPolicy;
 import org.elasticsearch.action.update.UpdateRequest;
 import org.elasticsearch.action.update.UpdateResponse;
+import org.elasticsearch.client.core.GetSourceRequest;
+import org.elasticsearch.client.core.GetSourceResponse;
 import org.elasticsearch.client.core.MultiTermVectorsRequest;
 import org.elasticsearch.client.core.MultiTermVectorsResponse;
 import org.elasticsearch.client.core.TermVectorsRequest;
@@ -64,6 +66,7 @@ import org.joda.time.format.DateTimeFormat;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicReference;
@@ -359,6 +362,71 @@ public class CrudIT extends ESRestHighLevelClientTestCase {
         }
     }
 
+    public void testGetSource() throws IOException {
+        {
+            GetSourceRequest getRequest = new GetSourceRequest("index", "id");
+            ElasticsearchException exception = expectThrows(ElasticsearchException.class,
+                () -> execute(getRequest, highLevelClient()::getSource, highLevelClient()::getSourceAsync));
+            assertEquals(RestStatus.NOT_FOUND, exception.status());
+            assertEquals("Elasticsearch exception [type=index_not_found_exception, reason=no such index [index]]", exception.getMessage());
+            assertEquals("index", exception.getMetadata("es.index").get(0));
+        }
+        IndexRequest index = new IndexRequest("index").id("id");
+        String document = "{\"field1\":\"value1\",\"field2\":\"value2\"}";
+        index.source(document, XContentType.JSON);
+        index.setRefreshPolicy(RefreshPolicy.IMMEDIATE);
+        highLevelClient().index(index, RequestOptions.DEFAULT);
+        {
+            GetSourceRequest getRequest = new GetSourceRequest("index", "id");
+            GetSourceResponse response = execute(getRequest, highLevelClient()::getSource, highLevelClient()::getSourceAsync);
+            Map<String, Object> expectedResponse = new HashMap<>();
+            expectedResponse.put("field1", "value1");
+            expectedResponse.put("field2", "value2");
+            assertEquals(expectedResponse, response.getSource());
+        }
+        {
+            GetSourceRequest getRequest = new GetSourceRequest("index", "does_not_exist");
+            ElasticsearchException exception = expectThrows(ElasticsearchException.class,
+                () -> execute(getRequest, highLevelClient()::getSource, highLevelClient()::getSourceAsync));
+            assertEquals(RestStatus.NOT_FOUND, exception.status());
+            assertEquals("Elasticsearch exception [type=resource_not_found_exception, " +
+                "reason=Document not found [index]/[does_not_exist]]", exception.getMessage());
+        }
+        {
+            GetSourceRequest getRequest = new GetSourceRequest("index", "id");
+            getRequest.fetchSourceContext(new FetchSourceContext(true, Strings.EMPTY_ARRAY, Strings.EMPTY_ARRAY));
+            GetSourceResponse response = execute(getRequest, highLevelClient()::getSource, highLevelClient()::getSourceAsync);
+            Map<String, Object> expectedResponse = new HashMap<>();
+            expectedResponse.put("field1", "value1");
+            expectedResponse.put("field2", "value2");
+            assertEquals(expectedResponse, response.getSource());
+        }
+        {
+            GetSourceRequest getRequest = new GetSourceRequest("index", "id");
+            getRequest.fetchSourceContext(new FetchSourceContext(true, new String[]{"field1"}, Strings.EMPTY_ARRAY));
+            GetSourceResponse response = execute(getRequest, highLevelClient()::getSource, highLevelClient()::getSourceAsync);
+            Map<String, Object> expectedResponse = new HashMap<>();
+            expectedResponse.put("field1", "value1");
+            assertEquals(expectedResponse, response.getSource());
+        }
+        {
+            GetSourceRequest getRequest = new GetSourceRequest("index", "id");
+            getRequest.fetchSourceContext(new FetchSourceContext(true, Strings.EMPTY_ARRAY, new String[]{"field1"}));
+            GetSourceResponse response = execute(getRequest, highLevelClient()::getSource, highLevelClient()::getSourceAsync);
+            Map<String, Object> expectedResponse = new HashMap<>();
+            expectedResponse.put("field2", "value2");
+            assertEquals(expectedResponse, response.getSource());
+        }
+        {
+            GetSourceRequest getRequest = new GetSourceRequest("index", "id");
+            getRequest.fetchSourceContext(new FetchSourceContext(false));
+            ElasticsearchException exception = expectThrows(ElasticsearchException.class,
+                () -> execute(getRequest, highLevelClient()::getSource, highLevelClient()::getSourceAsync));
+            assertEquals("Elasticsearch exception [type=action_request_validation_exception, " +
+                "reason=Validation Failed: 1: fetching source can not be disabled;]", exception.getMessage());
+        }
+    }
+
     public void testIndex() throws IOException {
         final XContentType xContentType = randomFrom(XContentType.values());
         {

+ 43 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java

@@ -53,6 +53,7 @@ import org.elasticsearch.action.support.replication.ReplicationRequest;
 import org.elasticsearch.action.update.UpdateRequest;
 import org.elasticsearch.client.RequestConverters.EndpointBuilder;
 import org.elasticsearch.client.core.CountRequest;
+import org.elasticsearch.client.core.GetSourceRequest;
 import org.elasticsearch.client.core.MultiTermVectorsRequest;
 import org.elasticsearch.client.core.TermVectorsRequest;
 import org.elasticsearch.client.indices.AnalyzeRequest;
@@ -160,6 +161,10 @@ public class RequestConvertersTests extends ESTestCase {
         doTestSourceExists((index, id) -> new GetRequest(index, id));
     }
 
+    public void testGetSource() throws IOException {
+        doTestGetSource((index, id) -> new GetSourceRequest(index, id));
+    }
+
     private static void doTestSourceExists(BiFunction<String, String, GetRequest> requestFunction) throws IOException {
         String index = randomAlphaOfLengthBetween(3, 10);
         String id = randomAlphaOfLengthBetween(3, 10);
@@ -198,6 +203,44 @@ public class RequestConvertersTests extends ESTestCase {
         assertNull(request.getEntity());
     }
 
+    private static void doTestGetSource(BiFunction<String, String, GetSourceRequest> requestFunction) throws IOException {
+        String index = randomAlphaOfLengthBetween(3, 10);
+        String id = randomAlphaOfLengthBetween(3, 10);
+        final GetSourceRequest getRequest = requestFunction.apply(index, id);
+
+        Map<String, String> expectedParams = new HashMap<>();
+        if (randomBoolean()) {
+            String preference = randomAlphaOfLengthBetween(3, 10);
+            getRequest.preference(preference);
+            expectedParams.put("preference", preference);
+        }
+        if (randomBoolean()) {
+            String routing = randomAlphaOfLengthBetween(3, 10);
+            getRequest.routing(routing);
+            expectedParams.put("routing", routing);
+        }
+        if (randomBoolean()) {
+            boolean realtime = randomBoolean();
+            getRequest.realtime(realtime);
+            if (realtime == false) {
+                expectedParams.put("realtime", "false");
+            }
+        }
+        if (randomBoolean()) {
+            boolean refresh = randomBoolean();
+            getRequest.refresh(refresh);
+            if (refresh) {
+                expectedParams.put("refresh", "true");
+            }
+        }
+        Request request = RequestConverters.getSource(getRequest);
+        assertEquals(HttpGet.METHOD_NAME, request.getMethod());
+        assertEquals("/" + index + "/_source/" + id, request.getEndpoint());
+
+        assertEquals(expectedParams, request.getParameters());
+        assertNull(request.getEntity());
+    }
+
     public void testMultiGet() throws IOException {
         Map<String, String> expectedParams = new HashMap<>();
         MultiGetRequest multiGetRequest = new MultiGetRequest();

+ 0 - 1
client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java

@@ -777,7 +777,6 @@ public class RestHighLevelClientTests extends ESTestCase {
             "create",
             "get_script_context",
             "get_script_languages",
-            "get_source",
             "indices.exists_type",
             "indices.get_upgrade",
             "indices.put_alias",

+ 73 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/core/GetSourceResponseTests.java

@@ -0,0 +1,73 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.core;
+
+import org.elasticsearch.client.AbstractResponseTestCase;
+import org.elasticsearch.common.bytes.BytesArray;
+import org.elasticsearch.common.bytes.BytesReference;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentHelper;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentType;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+
+public final class GetSourceResponseTests extends
+    AbstractResponseTestCase<GetSourceResponseTests.SourceOnlyResponse, GetSourceResponse> {
+
+    static class SourceOnlyResponse implements ToXContentObject {
+
+        private final BytesReference source;
+
+        SourceOnlyResponse(BytesReference source) {
+            this.source = source;
+        }
+
+        @Override
+        public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+            // this implementation copied from RestGetSourceAction.RestGetSourceResponseListener::buildResponse
+            try (InputStream stream = source.streamInput()) {
+                builder.rawValue(stream, XContentHelper.xContentType(source));
+            }
+            return builder;
+        }
+    }
+
+    @Override
+    protected SourceOnlyResponse createServerTestInstance(XContentType xContentType) {
+        BytesReference source = new BytesArray("{\"field\":\"value\"}");
+        return new SourceOnlyResponse(source);
+    }
+
+    @Override
+    protected GetSourceResponse doParseToClientInstance(XContentParser parser) throws IOException {
+        return GetSourceResponse.fromXContent(parser);
+    }
+
+    @Override
+    protected void assertInstances(SourceOnlyResponse serverTestInstance, GetSourceResponse clientInstance) {
+        assertThat(clientInstance.getSource(), equalTo(Map.of("field", "value")));
+    }
+}

+ 99 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/CRUDDocumentationIT.java

@@ -53,6 +53,8 @@ import org.elasticsearch.client.RequestOptions;
 import org.elasticsearch.client.Response;
 import org.elasticsearch.client.RestHighLevelClient;
 import org.elasticsearch.client.RethrottleRequest;
+import org.elasticsearch.client.core.GetSourceRequest;
+import org.elasticsearch.client.core.GetSourceResponse;
 import org.elasticsearch.client.core.MultiTermVectorsRequest;
 import org.elasticsearch.client.core.MultiTermVectorsResponse;
 import org.elasticsearch.client.core.TermVectorsRequest;
@@ -1398,6 +1400,103 @@ public class CRUDDocumentationIT extends ESRestHighLevelClientTestCase {
         }
     }
 
+    public void testGetSource() throws Exception {
+        RestHighLevelClient client = highLevelClient();
+        {
+            Request createIndex = new Request("PUT", "/posts");
+            createIndex.setJsonEntity(
+                "{\n" +
+                    "    \"mappings\" : {\n" +
+                    "        \"properties\" : {\n" +
+                    "            \"message\" : {\n" +
+                    "                \"type\": \"text\",\n" +
+                    "                \"store\": true\n" +
+                    "            }\n" +
+                    "        }\n" +
+                    "    }\n" +
+                    "}");
+            Response response = client().performRequest(createIndex);
+            assertEquals(200, response.getStatusLine().getStatusCode());
+
+            IndexRequest indexRequest = new IndexRequest("posts").id("1")
+                .source("user", "kimchy",
+                    "postDate", new Date(),
+                    "message", "trying out Elasticsearch");
+            IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);
+            assertEquals(DocWriteResponse.Result.CREATED, indexResponse.getResult());
+        }
+
+        // tag::get-source-request
+        GetSourceRequest getSourceRequest = new GetSourceRequest(
+            "posts", // <1>
+            "1");   // <2>
+        // end::get-source-request
+
+        //tag::get-source-request-optional
+        String[] includes = Strings.EMPTY_ARRAY;  // <2>
+        String[] excludes = new String[]{"postDate"};
+        getSourceRequest.fetchSourceContext(
+            new FetchSourceContext(true, includes, excludes)); // <1>
+        // end::get-source-request-optional
+
+        //tag::get-source-request-routing
+        getSourceRequest.routing("routing"); // <1>
+        //end::get-source-request-routing
+        //tag::get-source-request-preference
+        getSourceRequest.preference("preference"); // <1>
+        //end::get-source-request-preference
+        //tag::get-source-request-realtime
+        getSourceRequest.realtime(false); // <1>
+        //end::get-source-request-realtime
+        //tag::get-source-request-refresh
+        getSourceRequest.refresh(true); // <1>
+        //end::get-source-request-refresh
+
+        {
+            // tag::get-source-execute
+            GetSourceResponse response =
+                client.getSource(getSourceRequest, RequestOptions.DEFAULT);
+            // end::get-source-execute
+            // tag::get-source-response
+            Map<String, Object> source = response.getSource();
+            // end::get-source-response
+
+            Map<String, Object> expectSource = new HashMap<>();
+            expectSource.put("user", "kimchy");
+            expectSource.put("message", "trying out Elasticsearch");
+            assertEquals(expectSource, source);
+        }
+        {
+            GetSourceRequest request = new GetSourceRequest("posts", "1");
+
+            // tag::get-source-execute-listener
+            ActionListener<GetSourceResponse> listener =
+                new ActionListener<GetSourceResponse>() {
+                    @Override
+                    public void onResponse(GetSourceResponse getResponse) {
+                        // <1>
+                    }
+
+                    @Override
+                    public void onFailure(Exception e) {
+                        // <2>
+                    }
+                };
+            // end::get-source-execute-listener
+
+            // Replace the empty listener by a blocking listener in test
+            final CountDownLatch latch = new CountDownLatch(1);
+            listener = new LatchedActionListener<>(listener, latch);
+
+            //tag::get-source-execute-async
+            client.getSourceAsync(request, RequestOptions.DEFAULT, listener); // <1>
+            //end::get-source-execute-async
+
+            assertTrue(latch.await(30L, TimeUnit.SECONDS));
+        }
+
+    }
+
     public void testExists() throws Exception {
         RestHighLevelClient client = highLevelClient();
         // tag::exists-request

+ 72 - 0
docs/java-rest/high-level/document/get-source.asciidoc

@@ -0,0 +1,72 @@
+--
+:api: get-source
+:request: GetSourceRequest
+:response: GetSourceResponse
+--
+
+[id="{upid}-{api}"]
+=== Get Source API
+
+This API helps to get only the `_source` field of a document.
+
+[id="{upid}-{api}-request"]
+==== Get Source Request
+
+A +{request}+ requires the following arguments:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request]
+--------------------------------------------------
+<1> Index
+<2> Document id
+
+[id="{upid}-{api}-request-optional"]
+==== Optional arguments
+The following arguments can optionally be provided:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request-optional]
+--------------------------------------------------
+<1> `FetchSourceContext` 's first argument `fetchSource` must be `true`, otherwise
+`ElasticsearchException` get thrown
+<2> Arguments of the context `excludes` and `includes` are optional
+(see examples in Get API documentation)
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request-routing]
+--------------------------------------------------
+<1> Routing value
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request-preference]
+--------------------------------------------------
+<1> Preference value
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request-realtime]
+--------------------------------------------------
+<1> Set realtime flag to `false` (`true` by default)
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request-refresh]
+--------------------------------------------------
+<1> Perform a refresh before retrieving the document (`false` by default)
+
+include::../execution.asciidoc[]
+
+[id="{upid}-{api}-response"]
+==== Get Source Response
+
+The returned +{response}+ contains the field `source` that represents the
+source of a document as a map.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-response]
+--------------------------------------------------

+ 2 - 0
docs/java-rest/high-level/supported-apis.asciidoc

@@ -11,6 +11,7 @@ The Java High Level REST Client supports the following Document APIs:
 Single document APIs::
 * <<{upid}-index>>
 * <<{upid}-get>>
+* <<{upid}-get-source>>
 * <<{upid}-exists>>
 * <<{upid}-delete>>
 * <<{upid}-update>>
@@ -28,6 +29,7 @@ Multi-document APIs::
 
 include::document/index.asciidoc[]
 include::document/get.asciidoc[]
+include::document/get-source.asciidoc[]
 include::document/exists.asciidoc[]
 include::document/delete.asciidoc[]
 include::document/update.asciidoc[]