Browse Source

Rest: Add json in request body to scroll, clear scroll, and analyze API

Change analyze.asciidoc and scroll.asciidoc
Add json support to Analyze and Scroll, and clear scrollAPI
Add rest-api-spec/test

Closes #5866
Jun Ohtani 10 years ago
parent
commit
0955c127c0

+ 54 - 9
docs/reference/indices/analyze.asciidoc

@@ -9,26 +9,47 @@ analyzers:
 
 
 [source,js]
 [source,js]
 --------------------------------------------------
 --------------------------------------------------
-curl -XGET 'localhost:9200/_analyze?analyzer=standard' -d 'this is a test'
+curl -XGET 'localhost:9200/_analyze' -d '
+{
+  "analyzer" : "standard",
+  "text" : "this is a test"
+}'
 --------------------------------------------------
 --------------------------------------------------
 
 
+coming[2.0.0, body based parameters were added in 2.0.0]
+
 Or by building a custom transient analyzer out of tokenizers,
 Or by building a custom transient analyzer out of tokenizers,
 token filters and char filters. Token filters can use the shorter 'filters'
 token filters and char filters. Token filters can use the shorter 'filters'
 parameter name:
 parameter name:
 
 
 [source,js]
 [source,js]
 --------------------------------------------------
 --------------------------------------------------
-curl -XGET 'localhost:9200/_analyze?tokenizer=keyword&filters=lowercase' -d 'this is a test'
-
-curl -XGET 'localhost:9200/_analyze?tokenizer=keyword&token_filters=lowercase&char_filters=html_strip' -d 'this is a <b>test</b>'
+curl -XGET 'localhost:9200/_analyze' -d '
+{
+  "tokenizer" : "keyword",
+  "filters" : ["lowercase"],
+  "text" : "this is a test"
+}'
 
 
+curl -XGET 'localhost:9200/_analyze' -d '
+{
+  "tokenizer" : "keyword",
+  "token_filters" : ["lowercase"],
+  "char_filters" : ["html_strip"],
+  "text" : "this is a <b>test</b>"
+}'
 --------------------------------------------------
 --------------------------------------------------
 
 
+coming[2.0.0, body based parameters were added in 2.0.0]
+
 It can also run against a specific index:
 It can also run against a specific index:
 
 
 [source,js]
 [source,js]
 --------------------------------------------------
 --------------------------------------------------
-curl -XGET 'localhost:9200/test/_analyze?text=this+is+a+test'
+curl -XGET 'localhost:9200/test/_analyze' -d '
+{
+  "text" : "this is a test"
+}'
 --------------------------------------------------
 --------------------------------------------------
 
 
 The above will run an analysis on the "this is a test" text, using the
 The above will run an analysis on the "this is a test" text, using the
@@ -37,18 +58,42 @@ can also be provided to use a different analyzer:
 
 
 [source,js]
 [source,js]
 --------------------------------------------------
 --------------------------------------------------
-curl -XGET 'localhost:9200/test/_analyze?analyzer=whitespace' -d 'this is a test'
+curl -XGET 'localhost:9200/test/_analyze' -d '
+{
+  "analyzer" : "whitespace",
+  "text : "this is a test"
+}'
 --------------------------------------------------
 --------------------------------------------------
 
 
+coming[2.0.0, body based parameters were added in 2.0.0]
+
 Also, the analyzer can be derived based on a field mapping, for example:
 Also, the analyzer can be derived based on a field mapping, for example:
 
 
 [source,js]
 [source,js]
 --------------------------------------------------
 --------------------------------------------------
-curl -XGET 'localhost:9200/test/_analyze?field=obj1.field1' -d 'this is a test'
+curl -XGET 'localhost:9200/test/_analyze' -d '
+{
+  "field" : "obj1.field1",
+  "text" : "this is a test"
+}'
 --------------------------------------------------
 --------------------------------------------------
 
 
+coming[2.0.0, body based parameters were added in 2.0.0]
+
 Will cause the analysis to happen based on the analyzer configured in the
 Will cause the analysis to happen based on the analyzer configured in the
 mapping for `obj1.field1` (and if not, the default index analyzer).
 mapping for `obj1.field1` (and if not, the default index analyzer).
 
 
-Also, the text can be provided as part of the request body, and not as a
-parameter.
+All parameters can also supplied as request parameters. For example:
+
+[source,js]
+--------------------------------------------------
+curl -XGET 'localhost:9200/_analyze?tokenizer=keyword&filters=lowercase&text=this+is+a+test'
+--------------------------------------------------
+
+For backwards compatibility, we also accept the text parameter as the body of the request,
+provided it doesn't start with `{` :
+
+[source,js]
+--------------------------------------------------
+curl -XGET 'localhost:9200/_analyze?tokenizer=keyword&token_filters=lowercase&char_filters=html_strip' -d 'this is a <b>test</b>'
+--------------------------------------------------

+ 41 - 10
docs/reference/search/request/scroll.asciidoc

@@ -55,20 +55,35 @@ results.
 
 
 [source,js]
 [source,js]
 --------------------------------------------------
 --------------------------------------------------
-curl -XGET <1> 'localhost:9200/_search/scroll?scroll=1m' <2> <3> \
-     -d       'c2Nhbjs2OzM0NDg1ODpzRlBLc0FXNlNyNm5JWUc1' <4>
+curl -XGET <1> 'localhost:9200/_search/scroll' <2> -d'
+{
+    "scroll" : "1m", <3>
+    "scroll_id" : "c2Nhbjs2OzM0NDg1ODpzRlBLc0FXNlNyNm5JWUc1" <4>
+}
+'
 --------------------------------------------------
 --------------------------------------------------
+
+coming[2.0.0, body based parameters were added in 2.0.0]
+
 <1> `GET` or `POST` can be used.
 <1> `GET` or `POST` can be used.
 <2> The URL should not include the `index` or `type` name -- these
 <2> The URL should not include the `index` or `type` name -- these
     are specified in the original `search` request instead.
     are specified in the original `search` request instead.
 <3> The `scroll` parameter tells Elasticsearch to keep the search context open
 <3> The `scroll` parameter tells Elasticsearch to keep the search context open
     for another `1m`.
     for another `1m`.
-<4> The `scroll_id` can be passed in the request body or in the
-    query string as `?scroll_id=....`
+<4> The `scroll_id` parameter
 
 
 Each call to the `scroll` API returns the next batch of results until there
 Each call to the `scroll` API returns the next batch of results until there
 are no more results left to return, ie the `hits` array is empty.
 are no more results left to return, ie the `hits` array is empty.
 
 
+For backwards compatibility, `scroll_id` and `scroll` can be passed in the query string.
+And the `scroll_id` can be passed in the request body
+
+[source,js]
+--------------------------------------------------
+curl -XGET <1> 'localhost:9200/_search/scroll?scroll=1m' <2> <3> \
+     -d       'c2Nhbjs2OzM0NDg1ODpzRlBLc0FXNlNyNm5JWUc1' <4>
+--------------------------------------------------
+
 IMPORTANT: The initial search request and each subsequent scroll request
 IMPORTANT: The initial search request and each subsequent scroll request
 returns a new `scroll_id` -- only the most recent `scroll_id` should be
 returns a new `scroll_id` -- only the most recent `scroll_id` should be
 used.
 used.
@@ -168,19 +183,26 @@ clear a search context manually with the `clear-scroll` API:
 
 
 [source,js]
 [source,js]
 ---------------------------------------
 ---------------------------------------
-curl -XDELETE localhost:9200/_search/scroll \
-     -d 'c2Nhbjs2OzM0NDg1ODpzRlBLc0FXNlNyNm5JWUc1' <1>
+curl -XDELETE localhost:9200/_search/scroll -d '
+{
+    "scroll_id" : ["c2Nhbjs2OzM0NDg1ODpzRlBLc0FXNlNyNm5JWUc1"]
+}'
 ---------------------------------------
 ---------------------------------------
-<1> The `scroll_id` can be passed in the request body or in the query string.
 
 
-Multiple scroll IDs can be passed as comma separated values:
+coming[2.0.0, body based parameters were added in 2.0.0]
+
+Multiple scroll IDs can be passed as array:
 
 
 [source,js]
 [source,js]
 ---------------------------------------
 ---------------------------------------
-curl -XDELETE localhost:9200/_search/scroll \
-     -d 'c2Nhbjs2OzM0NDg1ODpzRlBLc0FXNlNyNm5JWUc1,aGVuRmV0Y2g7NTsxOnkxaDZ' <1>
+curl -XDELETE localhost:9200/_search/scroll -d '
+{
+    "scroll_id" : ["c2Nhbjs2OzM0NDg1ODpzRlBLc0FXNlNyNm5JWUc1", "aGVuRmV0Y2g7NTsxOnkxaDZ"]
+}'
 ---------------------------------------
 ---------------------------------------
 
 
+coming[2.0.0, body based parameters were added in 2.0.0]
+
 All search contexts can be cleared with the `_all` parameter:
 All search contexts can be cleared with the `_all` parameter:
 
 
 [source,js]
 [source,js]
@@ -188,3 +210,12 @@ All search contexts can be cleared with the `_all` parameter:
 curl -XDELETE localhost:9200/_search/scroll/_all
 curl -XDELETE localhost:9200/_search/scroll/_all
 ---------------------------------------
 ---------------------------------------
 
 
+The `scroll_id` can also be passed as a query string parameter or in the request body.
+Multiple scroll IDs can be passed as comma separated values:
+
+[source,js]
+---------------------------------------
+curl -XDELETE localhost:9200/_search/scroll \
+     -d 'c2Nhbjs2OzM0NDg1ODpzRlBLc0FXNlNyNm5JWUc1,aGVuRmV0Y2g7NTsxOnkxaDZ'
+---------------------------------------
+

+ 15 - 0
rest-api-spec/test/indices.analyze/10_analyze.yaml

@@ -48,3 +48,18 @@ setup:
     - length: { tokens: 2 }
     - length: { tokens: 2 }
     - match:     { tokens.0.token: Foo }
     - match:     { tokens.0.token: Foo }
     - match:     { tokens.1.token: Bar! }
     - match:     { tokens.1.token: Bar! }
+---
+"JSON in Body":
+    - do:
+        indices.analyze:
+          body: { "text": "Foo Bar", "filters": ["lowercase"], "tokenizer": keyword }
+    - length: {tokens: 1 }
+    - match:     { tokens.0.token: foo bar }
+---
+"Body params override query string":
+    - do:
+        indices.analyze:
+          text: Foo Bar
+          body: { "text": "Bar Foo", "filters": ["lowercase"], "tokenizer": keyword }
+    - length: {tokens: 1 }
+    - match:     { tokens.0.token: bar foo }

+ 61 - 2
rest-api-spec/test/scroll/10_basic.yaml

@@ -112,8 +112,7 @@
 
 
   - do:
   - do:
       scroll:
       scroll:
-        scroll_id: $scroll_id
-        scroll: 1m
+        body: { "scroll_id": "$scroll_id", "scroll": "1m"}
 
 
   - match: {hits.total:      2    }
   - match: {hits.total:      2    }
   - length: {hits.hits:      1    }
   - length: {hits.hits:      1    }
@@ -131,3 +130,63 @@
       clear_scroll:
       clear_scroll:
         scroll_id: $scroll_id
         scroll_id: $scroll_id
 
 
+---
+"Body params override query string":
+  - do:
+      indices.create:
+        index: test_scroll
+  - do:
+      index:
+        index:  test_scroll
+        type:   test
+        id:     42
+        body:   { foo: 1 }
+
+  - do:
+      index:
+        index:  test_scroll
+        type:   test
+        id:     43
+        body:   { foo: 2 }
+
+  - do:
+      indices.refresh: {}
+
+  - do:
+      search:
+        index: test_scroll
+        size: 1
+        scroll: 1m
+        sort: foo
+        body:
+          query:
+            match_all: {}
+
+  - set: {_scroll_id: scroll_id}
+  - match: {hits.total:      2    }
+  - length: {hits.hits:      1    }
+  - match: {hits.hits.0._id: "42" }
+
+  - do:
+      index:
+        index:  test_scroll
+        type:   test
+        id:     44
+        body:   { foo: 3 }
+
+  - do:
+      indices.refresh: {}
+
+  - do:
+      scroll:
+        scroll_id: invalid_scroll_id
+        body: { "scroll_id": "$scroll_id", "scroll": "1m"}
+
+  - match: {hits.total:      2    }
+  - length: {hits.hits:      1    }
+  - match: {hits.hits.0._id: "43" }
+
+  - do:
+      clear_scroll:
+        scroll_id: $scroll_id
+

+ 41 - 0
rest-api-spec/test/scroll/11_clear.yaml

@@ -37,3 +37,44 @@
         catch: missing
         catch: missing
         clear_scroll:
         clear_scroll:
           scroll_id: $scroll_id1
           scroll_id: $scroll_id1
+
+---
+"Body params override query string":
+  - do:
+      indices.create:
+          index:  test_scroll
+  - do:
+      index:
+          index:  test_scroll
+          type:   test
+          id:     42
+          body:   { foo: bar }
+
+  - do:
+      indices.refresh: {}
+
+  - do:
+      search:
+        index: test_scroll
+        search_type: scan
+        scroll: 1m
+        body:
+          query:
+            match_all: {}
+
+  - set: {_scroll_id: scroll_id1}
+
+  - do:
+      clear_scroll:
+        scroll_id: "invalid_scroll_id"
+        body: { "scroll_id": [ "$scroll_id1" ]}
+
+  - do:
+      catch: missing
+      scroll:
+        scroll_id: $scroll_id1
+
+  - do:
+        catch: missing
+        clear_scroll:
+          scroll_id: $scroll_id1

+ 8 - 14
src/main/java/org/elasticsearch/action/admin/indices/analyze/AnalyzeRequest.java

@@ -53,29 +53,23 @@ public class AnalyzeRequest extends SingleCustomOperationRequest<AnalyzeRequest>
     }
     }
 
 
     /**
     /**
-     * Constructs a new analyzer request for the provided text.
+     * Constructs a new analyzer request for the provided index.
      *
      *
-     * @param text The text to analyze
+     * @param index The text to analyze
      */
      */
-    public AnalyzeRequest(String text) {
-        this.text = text;
-    }
-
-    /**
-     * Constructs a new analyzer request for the provided index and text.
-     *
-     * @param index The index name
-     * @param text  The text to analyze
-     */
-    public AnalyzeRequest(@Nullable String index, String text) {
+    public AnalyzeRequest(String index) {
         this.index(index);
         this.index(index);
-        this.text = text;
     }
     }
 
 
     public String text() {
     public String text() {
         return this.text;
         return this.text;
     }
     }
 
 
+    public AnalyzeRequest text(String text) {
+        this.text = text;
+        return this;
+    }
+
     public AnalyzeRequest analyzer(String analyzer) {
     public AnalyzeRequest analyzer(String analyzer) {
         this.analyzer = analyzer;
         this.analyzer = analyzer;
         return this;
         return this;

+ 1 - 1
src/main/java/org/elasticsearch/action/admin/indices/analyze/AnalyzeRequestBuilder.java

@@ -32,7 +32,7 @@ public class AnalyzeRequestBuilder extends SingleCustomOperationRequestBuilder<A
     }
     }
 
 
     public AnalyzeRequestBuilder(IndicesAdminClient indicesClient, String index, String text) {
     public AnalyzeRequestBuilder(IndicesAdminClient indicesClient, String index, String text) {
-        super(indicesClient, new AnalyzeRequest(index, text));
+        super(indicesClient, new AnalyzeRequest(index).text(text));
     }
     }
 
 
     /**
     /**

+ 78 - 8
src/main/java/org/elasticsearch/rest/action/admin/indices/analyze/RestAnalyzeAction.java

@@ -18,15 +18,24 @@
  */
  */
 package org.elasticsearch.rest.action.admin.indices.analyze;
 package org.elasticsearch.rest.action.admin.indices.analyze;
 
 
+import com.google.common.collect.Lists;
 import org.elasticsearch.ElasticsearchIllegalArgumentException;
 import org.elasticsearch.ElasticsearchIllegalArgumentException;
 import org.elasticsearch.action.admin.indices.analyze.AnalyzeRequest;
 import org.elasticsearch.action.admin.indices.analyze.AnalyzeRequest;
 import org.elasticsearch.action.admin.indices.analyze.AnalyzeResponse;
 import org.elasticsearch.action.admin.indices.analyze.AnalyzeResponse;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.client.Client;
+import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.common.xcontent.XContentHelper;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.rest.*;
 import org.elasticsearch.rest.*;
 import org.elasticsearch.rest.action.support.RestToXContentListener;
 import org.elasticsearch.rest.action.support.RestToXContentListener;
 
 
+import java.io.IOException;
+import java.util.List;
+
 import static org.elasticsearch.rest.RestRequest.Method.GET;
 import static org.elasticsearch.rest.RestRequest.Method.GET;
 import static org.elasticsearch.rest.RestRequest.Method.POST;
 import static org.elasticsearch.rest.RestRequest.Method.POST;
 
 
@@ -47,14 +56,8 @@ public class RestAnalyzeAction extends BaseRestHandler {
     @Override
     @Override
     public void handleRequest(final RestRequest request, final RestChannel channel, final Client client) {
     public void handleRequest(final RestRequest request, final RestChannel channel, final Client client) {
         String text = request.param("text");
         String text = request.param("text");
-        if (text == null && request.hasContent()) {
-            text = request.content().toUtf8();
-        }
-        if (text == null) {
-            throw new ElasticsearchIllegalArgumentException("text is missing");
-        }
-
-        AnalyzeRequest analyzeRequest = new AnalyzeRequest(request.param("index"), text);
+        AnalyzeRequest analyzeRequest = new AnalyzeRequest(request.param("index"));
+        analyzeRequest.text(text);
         analyzeRequest.listenerThreaded(false);
         analyzeRequest.listenerThreaded(false);
         analyzeRequest.preferLocal(request.paramAsBoolean("prefer_local", analyzeRequest.preferLocalShard()));
         analyzeRequest.preferLocal(request.paramAsBoolean("prefer_local", analyzeRequest.preferLocalShard()));
         analyzeRequest.analyzer(request.param("analyzer"));
         analyzeRequest.analyzer(request.param("analyzer"));
@@ -62,6 +65,73 @@ public class RestAnalyzeAction extends BaseRestHandler {
         analyzeRequest.tokenizer(request.param("tokenizer"));
         analyzeRequest.tokenizer(request.param("tokenizer"));
         analyzeRequest.tokenFilters(request.paramAsStringArray("token_filters", request.paramAsStringArray("filters", analyzeRequest.tokenFilters())));
         analyzeRequest.tokenFilters(request.paramAsStringArray("token_filters", request.paramAsStringArray("filters", analyzeRequest.tokenFilters())));
         analyzeRequest.charFilters(request.paramAsStringArray("char_filters", analyzeRequest.charFilters()));
         analyzeRequest.charFilters(request.paramAsStringArray("char_filters", analyzeRequest.charFilters()));
+
+        if (request.hasContent()) {
+            XContentType type = XContentFactory.xContentType(request.content());
+            if (type == null) {
+                if (text == null) {
+                    text = request.content().toUtf8();
+                    analyzeRequest.text(text);
+                }
+            } else {
+                // NOTE: if rest request with xcontent body has request parameters, the parameters does not override xcontent values
+                buildFromContent(request.content(), analyzeRequest);
+            }
+        }
+
         client.admin().indices().analyze(analyzeRequest, new RestToXContentListener<AnalyzeResponse>(channel));
         client.admin().indices().analyze(analyzeRequest, new RestToXContentListener<AnalyzeResponse>(channel));
     }
     }
+
+    public static void buildFromContent(BytesReference content, AnalyzeRequest analyzeRequest) throws ElasticsearchIllegalArgumentException {
+        try (XContentParser parser = XContentHelper.createParser(content)) {
+            if (parser.nextToken() != XContentParser.Token.START_OBJECT) {
+                throw new ElasticsearchIllegalArgumentException("Malforrmed content, must start with an object");
+            } else {
+                XContentParser.Token token;
+                String currentFieldName = null;
+                while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+                    if (token == XContentParser.Token.FIELD_NAME) {
+                        currentFieldName = parser.currentName();
+                    } else if ("prefer_local".equals(currentFieldName) && token == XContentParser.Token.VALUE_BOOLEAN) {
+                        analyzeRequest.preferLocal(parser.booleanValue());
+                    } else if ("text".equals(currentFieldName) && token == XContentParser.Token.VALUE_STRING) {
+                            analyzeRequest.text(parser.text());
+                    } else if ("analyzer".equals(currentFieldName) && token == XContentParser.Token.VALUE_STRING) {
+                        analyzeRequest.analyzer(parser.text());
+                    } else if ("field".equals(currentFieldName) && token == XContentParser.Token.VALUE_STRING) {
+                        analyzeRequest.field(parser.text());
+                    } else if ("tokenizer".equals(currentFieldName) && token == XContentParser.Token.VALUE_STRING) {
+                        analyzeRequest.tokenizer(parser.text());
+                    } else if (("token_filters".equals(currentFieldName) || "filters".equals(currentFieldName)) && token == XContentParser.Token.START_ARRAY) {
+                        List<String> filters = Lists.newArrayList();
+                        while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
+                            if (token.isValue() == false) {
+                                throw new ElasticsearchIllegalArgumentException(currentFieldName + " array element should only contain token filter's name");
+                            }
+                            filters.add(parser.text());
+                        }
+                        analyzeRequest.tokenFilters(filters.toArray(new String[0]));
+                    } else if ("char_filters".equals(currentFieldName) && token == XContentParser.Token.START_ARRAY) {
+                        List<String> charFilters = Lists.newArrayList();
+                        while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
+                            if (token.isValue() == false) {
+                                throw new ElasticsearchIllegalArgumentException(currentFieldName + " array element should only contain char filter's name");
+                            }
+                            charFilters.add(parser.text());
+                        }
+                        analyzeRequest.tokenFilters(charFilters.toArray(new String[0]));
+                    } else {
+                        throw new ElasticsearchIllegalArgumentException("Unknown parameter [" + currentFieldName + "] in request body or parameter is of the wrong type[" + token + "] ");
+                    }
+                }
+            }
+        } catch (IOException e) {
+            throw new ElasticsearchIllegalArgumentException("Failed to parse request body", e);
+        }
+    }
+
+
+
+
+
 }
 }

+ 47 - 4
src/main/java/org/elasticsearch/rest/action/search/RestClearScrollAction.java

@@ -19,16 +19,23 @@
 
 
 package org.elasticsearch.rest.action.search;
 package org.elasticsearch.rest.action.search;
 
 
+import org.elasticsearch.ElasticsearchIllegalArgumentException;
 import org.elasticsearch.action.search.ClearScrollRequest;
 import org.elasticsearch.action.search.ClearScrollRequest;
 import org.elasticsearch.action.search.ClearScrollResponse;
 import org.elasticsearch.action.search.ClearScrollResponse;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.common.xcontent.XContentHelper;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.rest.*;
 import org.elasticsearch.rest.*;
 import org.elasticsearch.rest.action.support.RestActions;
 import org.elasticsearch.rest.action.support.RestActions;
 import org.elasticsearch.rest.action.support.RestStatusToXContentListener;
 import org.elasticsearch.rest.action.support.RestStatusToXContentListener;
 
 
+import java.io.IOException;
 import java.util.Arrays;
 import java.util.Arrays;
 
 
 import static org.elasticsearch.rest.RestRequest.Method.DELETE;
 import static org.elasticsearch.rest.RestRequest.Method.DELETE;
@@ -48,12 +55,20 @@ public class RestClearScrollAction extends BaseRestHandler {
     @Override
     @Override
     public void handleRequest(final RestRequest request, final RestChannel channel, final Client client) {
     public void handleRequest(final RestRequest request, final RestChannel channel, final Client client) {
         String scrollIds = request.param("scroll_id");
         String scrollIds = request.param("scroll_id");
-        if (scrollIds == null) {
-            scrollIds = RestActions.getRestContent(request).toUtf8();
-        }
-
         ClearScrollRequest clearRequest = new ClearScrollRequest();
         ClearScrollRequest clearRequest = new ClearScrollRequest();
         clearRequest.setScrollIds(Arrays.asList(splitScrollIds(scrollIds)));
         clearRequest.setScrollIds(Arrays.asList(splitScrollIds(scrollIds)));
+        if (request.hasContent()) {
+            XContentType type = XContentFactory.xContentType(request.content());
+           if (type == null) {
+               scrollIds = RestActions.getRestContent(request).toUtf8();
+               clearRequest.setScrollIds(Arrays.asList(splitScrollIds(scrollIds)));
+           } else {
+               // NOTE: if rest request with xcontent body has request parameters, these parameters does not override xcontent value
+               clearRequest.setScrollIds(null);
+               buildFromContent(request.content(), clearRequest);
+           }
+        }
+
         client.clearScroll(clearRequest, new RestStatusToXContentListener<ClearScrollResponse>(channel));
         client.clearScroll(clearRequest, new RestStatusToXContentListener<ClearScrollResponse>(channel));
     }
     }
 
 
@@ -63,4 +78,32 @@ public class RestClearScrollAction extends BaseRestHandler {
         }
         }
         return Strings.splitStringByCommaToArray(scrollIds);
         return Strings.splitStringByCommaToArray(scrollIds);
     }
     }
+
+    public static void buildFromContent(BytesReference content, ClearScrollRequest clearScrollRequest) throws ElasticsearchIllegalArgumentException {
+        try (XContentParser parser = XContentHelper.createParser(content)) {
+            if (parser.nextToken() != XContentParser.Token.START_OBJECT) {
+                throw new ElasticsearchIllegalArgumentException("Malformed content, must start with an object");
+            } else {
+                XContentParser.Token token;
+                String currentFieldName = null;
+                while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+                    if (token == XContentParser.Token.FIELD_NAME) {
+                        currentFieldName = parser.currentName();
+                    } else if ("scroll_id".equals(currentFieldName) && token == XContentParser.Token.START_ARRAY) {
+                        while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
+                            if (token.isValue() == false) {
+                                throw new ElasticsearchIllegalArgumentException("scroll_id array element should only contain scroll_id");
+                            }
+                            clearScrollRequest.addScrollId(parser.text());
+                        }
+                    } else {
+                        throw new ElasticsearchIllegalArgumentException("Unknown parameter [" + currentFieldName + "] in request body or parameter is of the wrong type[" + token + "] ");
+                    }
+                }
+            }
+        } catch (IOException e) {
+            throw new ElasticsearchIllegalArgumentException("Failed to parse request body", e);
+        }
+    }
+
 }
 }

+ 48 - 4
src/main/java/org/elasticsearch/rest/action/search/RestSearchScrollAction.java

@@ -19,16 +19,25 @@
 
 
 package org.elasticsearch.rest.action.search;
 package org.elasticsearch.rest.action.search;
 
 
+import org.elasticsearch.ElasticsearchIllegalArgumentException;
 import org.elasticsearch.action.search.SearchResponse;
 import org.elasticsearch.action.search.SearchResponse;
 import org.elasticsearch.action.search.SearchScrollRequest;
 import org.elasticsearch.action.search.SearchScrollRequest;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.client.Client;
+import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.unit.TimeValue;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.common.xcontent.XContentHelper;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.rest.*;
 import org.elasticsearch.rest.*;
 import org.elasticsearch.rest.action.support.RestActions;
 import org.elasticsearch.rest.action.support.RestActions;
 import org.elasticsearch.rest.action.support.RestStatusToXContentListener;
 import org.elasticsearch.rest.action.support.RestStatusToXContentListener;
 import org.elasticsearch.search.Scroll;
 import org.elasticsearch.search.Scroll;
 
 
+import java.io.IOException;
+
 import static org.elasticsearch.common.unit.TimeValue.parseTimeValue;
 import static org.elasticsearch.common.unit.TimeValue.parseTimeValue;
 import static org.elasticsearch.rest.RestRequest.Method.GET;
 import static org.elasticsearch.rest.RestRequest.Method.GET;
 import static org.elasticsearch.rest.RestRequest.Method.POST;
 import static org.elasticsearch.rest.RestRequest.Method.POST;
@@ -51,16 +60,51 @@ public class RestSearchScrollAction extends BaseRestHandler {
     @Override
     @Override
     public void handleRequest(final RestRequest request, final RestChannel channel, final Client client) {
     public void handleRequest(final RestRequest request, final RestChannel channel, final Client client) {
         String scrollId = request.param("scroll_id");
         String scrollId = request.param("scroll_id");
-        if (scrollId == null) {
-            scrollId = RestActions.getRestContent(request).toUtf8();
-        }
-        SearchScrollRequest searchScrollRequest = new SearchScrollRequest(scrollId);
+        SearchScrollRequest searchScrollRequest = new SearchScrollRequest();
         searchScrollRequest.listenerThreaded(false);
         searchScrollRequest.listenerThreaded(false);
+        searchScrollRequest.scrollId(scrollId);
         String scroll = request.param("scroll");
         String scroll = request.param("scroll");
         if (scroll != null) {
         if (scroll != null) {
             searchScrollRequest.scroll(new Scroll(parseTimeValue(scroll, null)));
             searchScrollRequest.scroll(new Scroll(parseTimeValue(scroll, null)));
         }
         }
 
 
+        if (request.hasContent()) {
+            XContentType type = XContentFactory.xContentType(request.content());
+            if (type == null) {
+                if (scrollId == null) {
+                    scrollId = RestActions.getRestContent(request).toUtf8();
+                    searchScrollRequest.scrollId(scrollId);
+                }
+            } else {
+                // NOTE: if rest request with xcontent body has request parameters, these parameters override xcontent values
+                buildFromContent(request.content(), searchScrollRequest);
+            }
+        }
         client.searchScroll(searchScrollRequest, new RestStatusToXContentListener<SearchResponse>(channel));
         client.searchScroll(searchScrollRequest, new RestStatusToXContentListener<SearchResponse>(channel));
     }
     }
+
+    public static void buildFromContent(BytesReference content, SearchScrollRequest searchScrollRequest) throws ElasticsearchIllegalArgumentException {
+        try (XContentParser parser = XContentHelper.createParser(content)) {
+            if (parser.nextToken() != XContentParser.Token.START_OBJECT) {
+                throw new ElasticsearchIllegalArgumentException("Malforrmed content, must start with an object");
+            } else {
+                XContentParser.Token token;
+                String currentFieldName = null;
+                while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+                    if (token == XContentParser.Token.FIELD_NAME) {
+                        currentFieldName = parser.currentName();
+                    } else if ("scroll_id".equals(currentFieldName) && token == XContentParser.Token.VALUE_STRING) {
+                        searchScrollRequest.scrollId(parser.text());
+                    } else if ("scroll".equals(currentFieldName) && token == XContentParser.Token.VALUE_STRING) {
+                        searchScrollRequest.scroll(new Scroll(TimeValue.parseTimeValue(parser.text(), null)));
+                    } else {
+                        throw new ElasticsearchIllegalArgumentException("Unknown parameter [" + currentFieldName + "] in request body or parameter is of the wrong type[" + token + "] ");
+                    }
+                }
+            }
+        } catch (IOException e) {
+            throw new ElasticsearchIllegalArgumentException("Failed to parse request body", e);
+        }
+    }
+
 }
 }

+ 2 - 1
src/test/java/org/elasticsearch/action/IndicesRequestTests.java

@@ -180,7 +180,8 @@ public class IndicesRequestTests extends ElasticsearchIntegrationTest {
         String analyzeShardAction = AnalyzeAction.NAME + "[s]";
         String analyzeShardAction = AnalyzeAction.NAME + "[s]";
         interceptTransportActions(analyzeShardAction);
         interceptTransportActions(analyzeShardAction);
 
 
-        AnalyzeRequest analyzeRequest = new AnalyzeRequest(randomIndexOrAlias(), "text");
+        AnalyzeRequest analyzeRequest = new AnalyzeRequest(randomIndexOrAlias());
+        analyzeRequest.text("text");
         internalCluster().clientNodeClient().admin().indices().analyze(analyzeRequest).actionGet();
         internalCluster().clientNodeClient().admin().indices().analyze(analyzeRequest).actionGet();
 
 
         clearInterceptedActions();
         clearInterceptedActions();

+ 57 - 1
src/test/java/org/elasticsearch/indices/analyze/AnalyzeActionTests.java

@@ -21,15 +21,19 @@ package org.elasticsearch.indices.analyze;
 import org.elasticsearch.ElasticsearchException;
 import org.elasticsearch.ElasticsearchException;
 import org.elasticsearch.ElasticsearchIllegalArgumentException;
 import org.elasticsearch.ElasticsearchIllegalArgumentException;
 import org.elasticsearch.action.admin.indices.alias.Alias;
 import org.elasticsearch.action.admin.indices.alias.Alias;
+import org.elasticsearch.action.admin.indices.analyze.AnalyzeRequest;
 import org.elasticsearch.action.admin.indices.analyze.AnalyzeRequestBuilder;
 import org.elasticsearch.action.admin.indices.analyze.AnalyzeRequestBuilder;
 import org.elasticsearch.action.admin.indices.analyze.AnalyzeResponse;
 import org.elasticsearch.action.admin.indices.analyze.AnalyzeResponse;
+import org.elasticsearch.common.bytes.BytesReference;
+import org.elasticsearch.common.xcontent.*;
+import org.elasticsearch.rest.action.admin.indices.analyze.RestAnalyzeAction;
 import org.elasticsearch.test.ElasticsearchIntegrationTest;
 import org.elasticsearch.test.ElasticsearchIntegrationTest;
 import org.junit.Test;
 import org.junit.Test;
 
 
 import java.io.IOException;
 import java.io.IOException;
 
 
 import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
 import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
-import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
+import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.*;
 import static org.hamcrest.Matchers.*;
 import static org.hamcrest.Matchers.*;
 
 
 /**
 /**
@@ -191,4 +195,56 @@ public class AnalyzeActionTests extends ElasticsearchIntegrationTest {
     private static String indexOrAlias() {
     private static String indexOrAlias() {
         return randomBoolean() ? "test" : "alias";
         return randomBoolean() ? "test" : "alias";
     }
     }
+
+    @Test
+    public void testParseXContentForAnalyzeReuqest() throws Exception {
+        BytesReference content =  XContentFactory.jsonBuilder()
+            .startObject()
+            .field("text", "THIS IS A TEST")
+            .field("tokenizer", "keyword")
+            .array("filters", "lowercase")
+            .endObject().bytes();
+
+        AnalyzeRequest analyzeRequest = new AnalyzeRequest("for test");
+
+        RestAnalyzeAction.buildFromContent(content, analyzeRequest);
+
+        assertThat(analyzeRequest.text(), equalTo("THIS IS A TEST"));
+        assertThat(analyzeRequest.tokenizer(), equalTo("keyword"));
+        assertThat(analyzeRequest.tokenFilters(), equalTo(new String[]{"lowercase"}));
+    }
+
+    @Test
+    public void testParseXContentForAnalyzeRequestWithInvalidJsonThrowsException() throws Exception {
+        AnalyzeRequest analyzeRequest = new AnalyzeRequest("for test");
+        BytesReference invalidContent =  XContentFactory.jsonBuilder().startObject().value("invalid_json").endObject().bytes();
+
+        try {
+            RestAnalyzeAction.buildFromContent(invalidContent, analyzeRequest);
+            fail("shouldn't get here");
+        } catch (Exception e) {
+            assertThat(e, instanceOf(ElasticsearchIllegalArgumentException.class));
+            assertThat(e.getMessage(), equalTo("Failed to parse request body"));
+        }
+    }
+
+
+    @Test
+    public void testParseXContentForAnalyzeRequestWithUnknownParamThrowsException() throws Exception {
+        AnalyzeRequest analyzeRequest = new AnalyzeRequest("for test");
+        BytesReference invalidContent =XContentFactory.jsonBuilder()
+            .startObject()
+            .field("text", "THIS IS A TEST")
+            .field("unknown", "keyword")
+            .endObject().bytes();
+
+        try {
+            RestAnalyzeAction.buildFromContent(invalidContent, analyzeRequest);
+            fail("shouldn't get here");
+        } catch (Exception e) {
+            assertThat(e, instanceOf(ElasticsearchIllegalArgumentException.class));
+            assertThat(e.getMessage(), startsWith("Unknown parameter [unknown]"));
+        }
+    }
+
 }
 }

+ 96 - 9
src/test/java/org/elasticsearch/search/scroll/SearchScrollTests.java

@@ -20,17 +20,18 @@
 package org.elasticsearch.search.scroll;
 package org.elasticsearch.search.scroll;
 
 
 import org.elasticsearch.ElasticsearchIllegalArgumentException;
 import org.elasticsearch.ElasticsearchIllegalArgumentException;
-import org.elasticsearch.action.search.ClearScrollResponse;
-import org.elasticsearch.action.search.SearchRequestBuilder;
-import org.elasticsearch.action.search.SearchResponse;
-import org.elasticsearch.action.search.SearchType;
+import org.elasticsearch.action.search.*;
 import org.elasticsearch.cluster.metadata.IndexMetaData;
 import org.elasticsearch.cluster.metadata.IndexMetaData;
 import org.elasticsearch.common.Priority;
 import org.elasticsearch.common.Priority;
+import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.settings.ImmutableSettings;
 import org.elasticsearch.common.settings.ImmutableSettings;
 import org.elasticsearch.common.unit.TimeValue;
 import org.elasticsearch.common.unit.TimeValue;
 import org.elasticsearch.common.util.concurrent.UncategorizedExecutionException;
 import org.elasticsearch.common.util.concurrent.UncategorizedExecutionException;
+import org.elasticsearch.common.xcontent.XContentFactory;
 import org.elasticsearch.index.query.QueryBuilders;
 import org.elasticsearch.index.query.QueryBuilders;
 import org.elasticsearch.rest.RestStatus;
 import org.elasticsearch.rest.RestStatus;
+import org.elasticsearch.rest.action.search.RestClearScrollAction;
+import org.elasticsearch.rest.action.search.RestSearchScrollAction;
 import org.elasticsearch.search.SearchHit;
 import org.elasticsearch.search.SearchHit;
 import org.elasticsearch.search.sort.FieldSortBuilder;
 import org.elasticsearch.search.sort.FieldSortBuilder;
 import org.elasticsearch.search.sort.SortOrder;
 import org.elasticsearch.search.sort.SortOrder;
@@ -45,11 +46,7 @@ import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
 import static org.elasticsearch.index.query.QueryBuilders.queryStringQuery;
 import static org.elasticsearch.index.query.QueryBuilders.queryStringQuery;
 import static org.elasticsearch.index.query.QueryBuilders.termQuery;
 import static org.elasticsearch.index.query.QueryBuilders.termQuery;
 import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.*;
 import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.*;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.greaterThan;
-import static org.hamcrest.Matchers.instanceOf;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.*;
 
 
 /**
 /**
  *
  *
@@ -490,4 +487,94 @@ public class SearchScrollTests extends ElasticsearchIntegrationTest {
         assertHitCount(response, 1);
         assertHitCount(response, 1);
         assertThat(response.getHits().getHits().length, equalTo(0));
         assertThat(response.getHits().getHits().length, equalTo(0));
     }
     }
+
+    @Test
+    public void testParseSearchScrollRequest() throws Exception {
+        BytesReference content = XContentFactory.jsonBuilder()
+            .startObject()
+            .field("scroll_id", "SCROLL_ID")
+            .field("scroll", "1m")
+            .endObject().bytes();
+
+        SearchScrollRequest searchScrollRequest = new SearchScrollRequest();
+        RestSearchScrollAction.buildFromContent(content, searchScrollRequest);
+
+        assertThat(searchScrollRequest.scrollId(), equalTo("SCROLL_ID"));
+        assertThat(searchScrollRequest.scroll().keepAlive(), equalTo(TimeValue.parseTimeValue("1m", null)));
+    }
+
+    @Test
+    public void testParseSearchScrollRequestWithInvalidJsonThrowsException() throws Exception {
+        SearchScrollRequest searchScrollRequest = new SearchScrollRequest();
+        BytesReference invalidContent = XContentFactory.jsonBuilder().startObject()
+            .value("invalid_json").endObject().bytes();
+
+        try {
+            RestSearchScrollAction.buildFromContent(invalidContent, searchScrollRequest);
+            fail("expected parseContent failure");
+        } catch (Exception e) {
+            assertThat(e, instanceOf(ElasticsearchIllegalArgumentException.class));
+            assertThat(e.getMessage(), equalTo("Failed to parse request body"));
+        }
+    }
+
+    @Test
+    public void testParseSearchScrollRequestWithUnknownParamThrowsException() throws Exception {
+        SearchScrollRequest searchScrollRequest = new SearchScrollRequest();
+        BytesReference invalidContent = XContentFactory.jsonBuilder().startObject()
+            .field("scroll_id", "value_2")
+            .field("unknown", "keyword")
+            .endObject().bytes();
+
+        try {
+            RestSearchScrollAction.buildFromContent(invalidContent, searchScrollRequest);
+            fail("expected parseContent failure");
+        } catch (Exception e) {
+            assertThat(e, instanceOf(ElasticsearchIllegalArgumentException.class));
+            assertThat(e.getMessage(), startsWith("Unknown parameter [unknown]"));
+        }
+    }
+
+    @Test
+    public void testParseClearScrollRequest() throws Exception {
+        BytesReference content = XContentFactory.jsonBuilder().startObject()
+            .array("scroll_id", "value_1", "value_2")
+            .endObject().bytes();
+        ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
+        RestClearScrollAction.buildFromContent(content, clearScrollRequest);
+        assertThat(clearScrollRequest.scrollIds(), contains("value_1", "value_2"));
+    }
+
+    @Test
+    public void testParseClearScrollRequestWithInvalidJsonThrowsException() throws Exception {
+        BytesReference invalidContent = XContentFactory.jsonBuilder().startObject()
+            .value("invalid_json").endObject().bytes();
+        ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
+
+        try {
+            RestClearScrollAction.buildFromContent(invalidContent, clearScrollRequest);
+            fail("expected parseContent failure");
+        } catch (Exception e) {
+            assertThat(e, instanceOf(ElasticsearchIllegalArgumentException.class));
+            assertThat(e.getMessage(), equalTo("Failed to parse request body"));
+        }
+    }
+
+    @Test
+    public void testParseClearScrollRequestWithUnknownParamThrowsException() throws Exception {
+        BytesReference invalidContent = XContentFactory.jsonBuilder().startObject()
+            .array("scroll_id", "value_1", "value_2")
+            .field("unknown", "keyword")
+            .endObject().bytes();
+        ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
+
+        try {
+            RestClearScrollAction.buildFromContent(invalidContent, clearScrollRequest);
+            fail("expected parseContent failure");
+        } catch (Exception e) {
+            assertThat(e, instanceOf(ElasticsearchIllegalArgumentException.class));
+            assertThat(e.getMessage(), startsWith("Unknown parameter [unknown]"));
+        }
+    }
+
 }
 }