Explorar el Código

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

 Add json support to scroll, clear scroll, and analyze

Closes #5866
Jun Ohtani hace 10 años
padre
commit
16083d454c
Se han modificado 20 ficheros con 831 adiciones y 209 borrados
  1. 13 0
      docs/reference/indices/analyze.asciidoc
  2. 21 0
      docs/reference/search/request/scroll.asciidoc
  3. 28 82
      src/main/java/org/elasticsearch/action/admin/indices/analyze/AnalyzeRequest.java
  4. 40 6
      src/main/java/org/elasticsearch/action/admin/indices/analyze/AnalyzeRequestBuilder.java
  5. 143 0
      src/main/java/org/elasticsearch/action/admin/indices/analyze/AnalyzeSourceBuilder.java
  6. 121 23
      src/main/java/org/elasticsearch/action/admin/indices/analyze/TransportAnalyzeAction.java
  7. 41 22
      src/main/java/org/elasticsearch/action/search/ClearScrollRequest.java
  8. 22 2
      src/main/java/org/elasticsearch/action/search/ClearScrollRequestBuilder.java
  9. 47 9
      src/main/java/org/elasticsearch/action/search/SearchScrollRequest.java
  10. 33 5
      src/main/java/org/elasticsearch/action/search/SearchScrollRequestBuilder.java
  11. 94 0
      src/main/java/org/elasticsearch/action/search/SearchScrollSourceBuilder.java
  12. 30 6
      src/main/java/org/elasticsearch/action/search/TransportClearScrollAction.java
  13. 20 4
      src/main/java/org/elasticsearch/action/search/TransportSearchScrollAction.java
  14. 61 0
      src/main/java/org/elasticsearch/action/search/type/TransportSearchHelper.java
  15. 23 18
      src/main/java/org/elasticsearch/rest/action/admin/indices/analyze/RestAnalyzeAction.java
  16. 10 10
      src/main/java/org/elasticsearch/rest/action/search/RestClearScrollAction.java
  17. 15 13
      src/main/java/org/elasticsearch/rest/action/search/RestSearchScrollAction.java
  18. 2 1
      src/test/java/org/elasticsearch/action/IndicesRequestTests.java
  19. 28 2
      src/test/java/org/elasticsearch/indices/analyze/AnalyzeActionTests.java
  20. 39 6
      src/test/java/org/elasticsearch/search/scroll/SearchScrollTests.java

+ 13 - 0
docs/reference/indices/analyze.asciidoc

@@ -52,3 +52,16 @@ 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.
+
+And all parameter can be provided as JSON of the request body.
+[source,js]
+--------------------------------------------------
+curl -XGET 'localhost:9200/test/_analyze' -d '
+{
+    "text" : "this is a <b>test</b>",
+    "tokenizer" : "keyword",
+    "token_filters" : [ "lowercase" ],
+    "char_filters" : [ "html_strip" ]
+}
+'
+--------------------------------------------------

+ 21 - 0
docs/reference/search/request/scroll.asciidoc

@@ -69,6 +69,18 @@ curl -XGET <1> 'localhost:9200/_search/scroll?scroll=1m' <2> <3> \
 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.
 
+And you can use JSON in the request body.
+
+[source,js]
+--------------------------------------------------
+curl -XGET 'localhost:9200/_search/scroll' -d'
+{
+    "scroll_id" : "c2Nhbjs2OzM0NDg1ODpzRlBLc0FXNlNyNm5JWUc1",
+    "scroll" : "1m"
+}
+'
+--------------------------------------------------
+
 IMPORTANT: The initial search request and each subsequent scroll request
 returns a new `scroll_id` -- only the most recent `scroll_id` should be
 used.
@@ -184,3 +196,12 @@ All search contexts can be cleared with the `_all` parameter:
 curl -XDELETE localhost:9200/_search/scroll/_all
 ---------------------------------------
 
+And you can use JSON in the request body.
+
+[source,js]
+---------------------------------------
+curl -XDELETE localhost:9200/_search/scroll -d '
+{
+    "scroll_id" : ["c2Nhbjs2OzM0NDg1ODpzRlBLc0FXNlNyNm5JWUc1", "aGVuRmV0Y2g7NTsxOnkxaDZ"]
+}
+---------------------------------------

+ 28 - 82
src/main/java/org/elasticsearch/action/admin/indices/analyze/AnalyzeRequest.java

@@ -18,11 +18,12 @@
  */
 package org.elasticsearch.action.admin.indices.analyze;
 
-import org.elasticsearch.Version;
 import org.elasticsearch.action.ActionRequestValidationException;
 import org.elasticsearch.action.support.single.custom.SingleCustomOperationRequest;
+import org.elasticsearch.client.Requests;
 import org.elasticsearch.common.Nullable;
-import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.bytes.BytesArray;
+import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 
@@ -36,102 +37,56 @@ import static org.elasticsearch.action.ValidateActions.addValidationError;
  */
 public class AnalyzeRequest extends SingleCustomOperationRequest<AnalyzeRequest> {
 
-    private String text;
-
-    private String analyzer;
-
-    private String tokenizer;
-
-    private String[] tokenFilters = Strings.EMPTY_ARRAY;
-
-    private String[] charFilters = Strings.EMPTY_ARRAY;
-
-    private String field;
+    private BytesReference source;
+    private boolean unsafe;
 
     AnalyzeRequest() {
 
     }
-
-    /**
-     * Constructs a new analyzer request for the provided text.
-     *
-     * @param text 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(@Nullable String index) {
         this.index(index);
-        this.text = text;
     }
 
-    public String text() {
-        return this.text;
+    public BytesReference source() {
+        return source;
     }
 
-    public AnalyzeRequest analyzer(String analyzer) {
-        this.analyzer = analyzer;
-        return this;
-    }
-
-    public String analyzer() {
-        return this.analyzer;
-    }
-
-    public AnalyzeRequest tokenizer(String tokenizer) {
-        this.tokenizer = tokenizer;
-        return this;
-    }
-
-    public String tokenizer() {
-        return this.tokenizer;
+    @Override
+    public void beforeLocalFork() {
+        if (unsafe) {
+            source = source.copyBytesArray();
+            unsafe = false;
+        }
     }
 
-    public AnalyzeRequest tokenFilters(String... tokenFilters) {
-        this.tokenFilters = tokenFilters;
+    public AnalyzeRequest source(String source) {
+        this.source = new BytesArray(source);
+        this.unsafe = false;
         return this;
     }
 
-    public String[] tokenFilters() {
-        return this.tokenFilters;
-    }
-
-    public AnalyzeRequest charFilters(String... charFilters) {
-        this.charFilters = charFilters;
+    public AnalyzeRequest source(BytesReference source, boolean unsafe) {
+        this.source = source;
+        this.unsafe = unsafe;
         return this;
     }
 
-    public String[] charFilters() {
-        return this.charFilters;
-    }
-
-    public AnalyzeRequest field(String field) {
-        this.field = field;
+    public AnalyzeRequest source(AnalyzeSourceBuilder sourceBuilder) {
+        this.source = sourceBuilder.buildAsBytes(Requests.CONTENT_TYPE);
+        this.unsafe = false;
         return this;
     }
 
-    public String field() {
-        return this.field;
-    }
-
     @Override
     public ActionRequestValidationException validate() {
         ActionRequestValidationException validationException = super.validate();
-        if (text == null) {
-            validationException = addValidationError("text is missing", validationException);
-        }
-        if (tokenFilters == null) {
-            validationException = addValidationError("token filters must not be null", validationException);
-        }
-        if (charFilters == null) {
-            validationException = addValidationError("char filters must not be null", validationException);
+        if (source == null) {
+            validationException = addValidationError("source is missing", validationException);
         }
         return validationException;
     }
@@ -139,22 +94,13 @@ public class AnalyzeRequest extends SingleCustomOperationRequest<AnalyzeRequest>
     @Override
     public void readFrom(StreamInput in) throws IOException {
         super.readFrom(in);
-        text = in.readString();
-        analyzer = in.readOptionalString();
-        tokenizer = in.readOptionalString();
-        tokenFilters = in.readStringArray();
-        charFilters = in.readStringArray();
-        field = in.readOptionalString();
+        unsafe = false;
+        source = in.readBytesReference();
     }
 
     @Override
     public void writeTo(StreamOutput out) throws IOException {
         super.writeTo(out);
-        out.writeString(text);
-        out.writeOptionalString(analyzer);
-        out.writeOptionalString(tokenizer);
-        out.writeStringArray(tokenFilters);
-        out.writeStringArray(charFilters);
-        out.writeOptionalString(field);
+        out.writeBytesReference(source);
     }
 }

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

@@ -21,18 +21,22 @@ package org.elasticsearch.action.admin.indices.analyze;
 import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.support.single.custom.SingleCustomOperationRequestBuilder;
 import org.elasticsearch.client.IndicesAdminClient;
+import org.elasticsearch.common.bytes.BytesReference;
 
 /**
  *
  */
 public class AnalyzeRequestBuilder extends SingleCustomOperationRequestBuilder<AnalyzeRequest, AnalyzeResponse, AnalyzeRequestBuilder> {
 
+    private AnalyzeSourceBuilder builder;
+
     public AnalyzeRequestBuilder(IndicesAdminClient indicesClient) {
         super(indicesClient, new AnalyzeRequest());
     }
 
     public AnalyzeRequestBuilder(IndicesAdminClient indicesClient, String index, String text) {
-        super(indicesClient, new AnalyzeRequest(index, text));
+        super(indicesClient, new AnalyzeRequest(index));
+        sourceBuilder().setText(text);
     }
 
     /**
@@ -50,7 +54,7 @@ public class AnalyzeRequestBuilder extends SingleCustomOperationRequestBuilder<A
      * @param analyzer The analyzer name.
      */
     public AnalyzeRequestBuilder setAnalyzer(String analyzer) {
-        request.analyzer(analyzer);
+        sourceBuilder().setAnalyzer(analyzer);
         return this;
     }
 
@@ -59,7 +63,7 @@ public class AnalyzeRequestBuilder extends SingleCustomOperationRequestBuilder<A
      * to be set.
      */
     public AnalyzeRequestBuilder setField(String field) {
-        request.field(field);
+        sourceBuilder().setField(field);
         return this;
     }
 
@@ -68,7 +72,7 @@ public class AnalyzeRequestBuilder extends SingleCustomOperationRequestBuilder<A
      * analyzer.
      */
     public AnalyzeRequestBuilder setTokenizer(String tokenizer) {
-        request.tokenizer(tokenizer);
+        sourceBuilder().setTokenizer(tokenizer);
         return this;
     }
 
@@ -76,7 +80,7 @@ public class AnalyzeRequestBuilder extends SingleCustomOperationRequestBuilder<A
      * Sets token filters that will be used on top of a tokenizer provided.
      */
     public AnalyzeRequestBuilder setTokenFilters(String... tokenFilters) {
-        request.tokenFilters(tokenFilters);
+        sourceBuilder().setTokenFilters(tokenFilters);
         return this;
     }
 
@@ -84,12 +88,42 @@ public class AnalyzeRequestBuilder extends SingleCustomOperationRequestBuilder<A
      * Sets char filters that will be used before the tokenizer.
      */
     public AnalyzeRequestBuilder setCharFilters(String... charFilters) {
-        request.charFilters(charFilters);
+        sourceBuilder().setCharFilters(charFilters);
+        return this;
+    }
+
+    public AnalyzeRequestBuilder setSource(String source) {
+        request.source(source);
         return this;
     }
 
+    public AnalyzeRequestBuilder setSource(BytesReference source, boolean unsafe) {
+        request.source(source, unsafe);
+        return this;
+    }
+
+    public AnalyzeRequestBuilder setSource(AnalyzeSourceBuilder sourceBuilder) {
+        request.source(sourceBuilder);
+        return this;
+    }
+
+    public AnalyzeRequestBuilder setText(String text) {
+        sourceBuilder().setText(text);
+        return this;
+    }
+
+    private AnalyzeSourceBuilder sourceBuilder() {
+        if (builder == null) {
+            builder = new AnalyzeSourceBuilder();
+        }
+        return builder;
+    }
+
     @Override
     protected void doExecute(ActionListener<AnalyzeResponse> listener) {
+        if (builder != null && request.source() == null) {
+            request.source(builder);
+        }
         client.analyze(request, listener);
     }
 }

+ 143 - 0
src/main/java/org/elasticsearch/action/admin/indices/analyze/AnalyzeSourceBuilder.java

@@ -0,0 +1,143 @@
+/*
+ * 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.action.admin.indices.analyze;
+
+import com.google.common.collect.Lists;
+import org.elasticsearch.common.bytes.BytesReference;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.common.xcontent.XContentType;
+import org.elasticsearch.search.builder.SearchSourceBuilderException;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Builder to create the analyze rquest body.
+ */
+public class AnalyzeSourceBuilder implements ToXContent {
+
+    private String text;
+    private String analyzer;
+    private String tokenizer;
+    private List<String> tokenFilters;
+    private List<String> charFilters;
+    private String field;
+    private boolean preferLocal;
+
+    public AnalyzeSourceBuilder setText(String text) {
+        this.text = text;
+        return this;
+    }
+
+    public AnalyzeSourceBuilder setAnalyzer(String analyzer) {
+        this.analyzer = analyzer;
+        return this;
+    }
+
+    public AnalyzeSourceBuilder setTokenizer(String tokenizer) {
+        this.tokenizer = tokenizer;
+        return this;
+    }
+
+    public AnalyzeSourceBuilder setTokenFilters(String... tokenFilters) {
+        if (tokenFilters != null) {
+            if (this.tokenFilters == null) {
+                this.tokenFilters = Lists.newArrayList(tokenFilters);
+            }
+        }
+        return this;
+    }
+
+    public AnalyzeSourceBuilder setTokenFilters(List<String> tokenFilters) {
+        this.tokenFilters = tokenFilters;
+        return this;
+    }
+
+    public AnalyzeSourceBuilder setCharFilters(List<String> charFilters) {
+        this.charFilters = charFilters;
+        return this;
+    }
+
+    public AnalyzeSourceBuilder setCharFilters(String... charFilters) {
+        if (charFilters != null) {
+            if (this.charFilters == null) {
+                this.charFilters = Lists.newArrayList(charFilters);
+            }
+        }
+        return this;
+    }
+
+    public AnalyzeSourceBuilder setField(String field) {
+        this.field = field;
+        return this;
+    }
+
+    public AnalyzeSourceBuilder setPreferLocal(boolean preferLocal) {
+        this.preferLocal = preferLocal;
+        return this;
+    }
+
+    @Override
+    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+        builder.startObject();
+        if (text != null) {
+            builder.field("text", text);
+        }
+        if (analyzer != null) {
+            builder.field("analyzer", analyzer);
+        }
+        if (tokenizer != null) {
+            builder.field("tokenizer", tokenizer);
+        }
+        if (tokenFilters != null) {
+            builder.startArray("filters");
+            for (String tokenFilter : tokenFilters) {
+                builder.value(tokenFilter);
+            }
+            builder.endArray();
+        }
+        if (charFilters != null) {
+            builder.startArray("char_filters");
+            for (String charFilter : charFilters) {
+                builder.value(charFilter);
+            }
+            builder.endArray();
+        }
+        if (field != null) {
+            builder.field("field", field);
+        }
+        builder.field("prefer_local", preferLocal);
+        builder.endObject();
+        return builder;
+    }
+
+
+    public BytesReference buildAsBytes(XContentType contentType) throws SearchSourceBuilderException {
+        try {
+            XContentBuilder builder = XContentFactory.contentBuilder(contentType);
+            toXContent(builder, ToXContent.EMPTY_PARAMS);
+            return builder.bytes();
+        } catch (Exception e) {
+            throw new SearchSourceBuilderException("Failed to build search source", e);
+        }
+    }
+}

+ 121 - 23
src/main/java/org/elasticsearch/action/admin/indices/analyze/TransportAnalyzeAction.java

@@ -27,6 +27,7 @@ import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
 import org.apache.lucene.analysis.tokenattributes.TypeAttribute;
 import org.elasticsearch.ElasticsearchException;
 import org.elasticsearch.ElasticsearchIllegalArgumentException;
+import org.elasticsearch.ElasticsearchParseException;
 import org.elasticsearch.Version;
 import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.support.ActionFilters;
@@ -39,10 +40,12 @@ import org.elasticsearch.cluster.routing.ShardsIterator;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.ImmutableSettings;
 import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.XContentHelper;
+import org.elasticsearch.common.xcontent.support.XContentMapValues;
+import org.elasticsearch.index.IndexService;
 import org.elasticsearch.index.analysis.*;
 import org.elasticsearch.index.mapper.FieldMapper;
 import org.elasticsearch.index.mapper.internal.AllFieldMapper;
-import org.elasticsearch.index.IndexService;
 import org.elasticsearch.index.shard.ShardId;
 import org.elasticsearch.indices.IndicesService;
 import org.elasticsearch.indices.analysis.IndicesAnalysisService;
@@ -53,6 +56,7 @@ import org.elasticsearch.transport.TransportService;
 
 import java.io.IOException;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Transport action used to execute analyze requests
@@ -117,17 +121,18 @@ public class TransportAnalyzeAction extends TransportSingleCustomOperationAction
         if (shardId != null) {
             indexService = indicesService.indexServiceSafe(shardId.getIndex());
         }
+        AnalyzeContext context = parseSource(request);
         Analyzer analyzer = null;
         boolean closeAnalyzer = false;
         String field = null;
-        if (request.field() != null) {
+        if (context.field() != null) {
             if (indexService == null) {
                 throw new ElasticsearchIllegalArgumentException("No index provided, and trying to analyzer based on a specific field which requires the index parameter");
             }
-            FieldMapper<?> fieldMapper = indexService.mapperService().smartNameFieldMapper(request.field());
+            FieldMapper<?> fieldMapper = indexService.mapperService().smartNameFieldMapper(context.field());
             if (fieldMapper != null) {
                 if (fieldMapper.isNumeric()) {
-                    throw new ElasticsearchIllegalArgumentException("Can't process field [" + request.field() + "], Analysis requests are not supported on numeric fields");
+                    throw new ElasticsearchIllegalArgumentException("Can't process field [" + context.field() + "], Analysis requests are not supported on numeric fields");
                 }
                 analyzer = fieldMapper.indexAnalyzer();
                 field = fieldMapper.names().indexName();
@@ -141,35 +146,35 @@ public class TransportAnalyzeAction extends TransportSingleCustomOperationAction
                 field = AllFieldMapper.NAME;
             }
         }
-        if (analyzer == null && request.analyzer() != null) {
+        if (analyzer == null && context.analyzer() != null) {
             if (indexService == null) {
-                analyzer = indicesAnalysisService.analyzer(request.analyzer());
+                analyzer = indicesAnalysisService.analyzer(context.analyzer());
             } else {
-                analyzer = indexService.analysisService().analyzer(request.analyzer());
+                analyzer = indexService.analysisService().analyzer(context.analyzer());
             }
             if (analyzer == null) {
-                throw new ElasticsearchIllegalArgumentException("failed to find analyzer [" + request.analyzer() + "]");
+                throw new ElasticsearchIllegalArgumentException("failed to find analyzer [" + context.analyzer() + "]");
             }
-        } else if (request.tokenizer() != null) {
+        } else if (context.tokenizer() != null) {
             TokenizerFactory tokenizerFactory;
             if (indexService == null) {
-                TokenizerFactoryFactory tokenizerFactoryFactory = indicesAnalysisService.tokenizerFactoryFactory(request.tokenizer());
+                TokenizerFactoryFactory tokenizerFactoryFactory = indicesAnalysisService.tokenizerFactoryFactory(context.tokenizer());
                 if (tokenizerFactoryFactory == null) {
-                    throw new ElasticsearchIllegalArgumentException("failed to find global tokenizer under [" + request.tokenizer() + "]");
+                    throw new ElasticsearchIllegalArgumentException("failed to find global tokenizer under [" + context.tokenizer() + "]");
                 }
-                tokenizerFactory = tokenizerFactoryFactory.create(request.tokenizer(), DEFAULT_SETTINGS);
+                tokenizerFactory = tokenizerFactoryFactory.create(context.tokenizer(), DEFAULT_SETTINGS);
             } else {
-                tokenizerFactory = indexService.analysisService().tokenizer(request.tokenizer());
+                tokenizerFactory = indexService.analysisService().tokenizer(context.tokenizer());
                 if (tokenizerFactory == null) {
-                    throw new ElasticsearchIllegalArgumentException("failed to find tokenizer under [" + request.tokenizer() + "]");
+                    throw new ElasticsearchIllegalArgumentException("failed to find tokenizer under [" + context.tokenizer() + "]");
                 }
             }
 
             TokenFilterFactory[] tokenFilterFactories = new TokenFilterFactory[0];
-            if (request.tokenFilters() != null && request.tokenFilters().length > 0) {
-                tokenFilterFactories = new TokenFilterFactory[request.tokenFilters().length];
-                for (int i = 0; i < request.tokenFilters().length; i++) {
-                    String tokenFilterName = request.tokenFilters()[i];
+            if (context.tokenFilters() != null && context.tokenFilters().isEmpty() == false) {
+                tokenFilterFactories = new TokenFilterFactory[context.tokenFilters().size()];
+                for (int i = 0; i < context.tokenFilters().size(); i++) {
+                    String tokenFilterName = context.tokenFilters().get(i);
                     if (indexService == null) {
                         TokenFilterFactoryFactory tokenFilterFactoryFactory = indicesAnalysisService.tokenFilterFactoryFactory(tokenFilterName);
                         if (tokenFilterFactoryFactory == null) {
@@ -189,10 +194,10 @@ public class TransportAnalyzeAction extends TransportSingleCustomOperationAction
             }
 
             CharFilterFactory[] charFilterFactories = new CharFilterFactory[0];
-            if (request.charFilters() != null && request.charFilters().length > 0) {
-                charFilterFactories = new CharFilterFactory[request.charFilters().length];
-                for (int i = 0; i < request.charFilters().length; i++) {
-                    String charFilterName = request.charFilters()[i];
+            if (context.charFilters() != null && context.charFilters().isEmpty() == false) {
+                charFilterFactories = new CharFilterFactory[context.charFilters().size()];
+                for (int i = 0; i < context.charFilters().size(); i++) {
+                    String charFilterName = context.charFilters().get(i);
                     if (indexService == null) {
                         CharFilterFactoryFactory charFilterFactoryFactory = indicesAnalysisService.charFilterFactoryFactory(charFilterName);
                         if (charFilterFactoryFactory == null) {
@@ -227,7 +232,7 @@ public class TransportAnalyzeAction extends TransportSingleCustomOperationAction
         List<AnalyzeResponse.AnalyzeToken> tokens = Lists.newArrayList();
         TokenStream stream = null;
         try {
-            stream = analyzer.tokenStream(field, request.text());
+            stream = analyzer.tokenStream(field, context.text());
             stream.reset();
             CharTermAttribute term = stream.addAttribute(CharTermAttribute.class);
             PositionIncrementAttribute posIncr = stream.addAttribute(PositionIncrementAttribute.class);
@@ -261,6 +266,99 @@ public class TransportAnalyzeAction extends TransportSingleCustomOperationAction
         return new AnalyzeResponse(tokens);
     }
 
+
+    private AnalyzeContext parseSource(AnalyzeRequest request) {
+        AnalyzeContext context = new AnalyzeContext();
+        try {
+            Map<String, Object> contentMap = XContentHelper.convertToMap(request.source(), false).v2();
+            for (Map.Entry<String, Object> entry : contentMap.entrySet()) {
+                String name = entry.getKey();
+                if ("prefer_local".equals(name)) {
+                    request.preferLocal(XContentMapValues.nodeBooleanValue(entry.getValue(), request.preferLocalShard()));
+                } else if ("analyzer".equals(name)) {
+                    context.analyzer(XContentMapValues.nodeStringValue(entry.getValue(), null));
+                } else if ("field".equals(name)) {
+                    context.field(XContentMapValues.nodeStringValue(entry.getValue(), null));
+                } else if ("tokenizer".equals(name)) {
+                    context.tokenizer(XContentMapValues.nodeStringValue(entry.getValue(), null));
+                } else if ("token_filters".equals(name) || "filters".equals(name)) {
+                    if (XContentMapValues.isArray(entry.getValue())) {
+                        context.tokenFilters((List<String>) entry.getValue());
+                    }
+                } else if ("char_filters".equals(name)) {
+                    if (XContentMapValues.isArray(entry.getValue())) {
+                        context.charFilters((List<String>) entry.getValue());
+                    }
+                } else if ("text".equals(name)) {
+                    context.text(XContentMapValues.nodeStringValue(entry.getValue(), null));
+                }
+            }
+        } catch (ElasticsearchParseException e) {
+            throw new ElasticsearchIllegalArgumentException("Failed to parse request body", e);
+        }
+        if (context.text() == null) {
+            throw new ElasticsearchIllegalArgumentException("text is missing");
+        }
+        return context;
+    }
+
+    private class AnalyzeContext {
+        private String text;
+        private String analyzer;
+        private String tokenizer;
+        private List<String> tokenFilters = Lists.newArrayList();
+        private List<String> charFilters = Lists.newArrayList();
+        private String field;
+
+        public String text() {
+            return text;
+        }
+
+        public String analyzer() {
+            return analyzer;
+        }
+
+        public String tokenizer() {
+            return tokenizer;
+        }
+
+        public List<String> tokenFilters() {
+            return tokenFilters;
+        }
+
+        public List<String> charFilters() {
+            return charFilters;
+        }
+
+        public String field() {
+            return field;
+        }
+
+        public void text(String text) {
+            this.text = text;
+        }
+
+        public void analyzer(String analyzer) {
+            this.analyzer = analyzer;
+        }
+
+        public void tokenizer(String tokenizer) {
+            this.tokenizer = tokenizer;
+        }
+
+        public void tokenFilters(List<String> tokenFilters) {
+            this.tokenFilters = tokenFilters;
+        }
+
+        public void charFilters(List<String> charFilters) {
+            this.charFilters = charFilters;
+        }
+
+        public void field(String field) {
+            this.field = field;
+        }
+    }
+
     private class TransportHandler extends BaseTransportRequestHandler<AnalyzeRequest> {
 
         @Override

+ 41 - 22
src/main/java/org/elasticsearch/action/search/ClearScrollRequest.java

@@ -19,51 +19,74 @@
 
 package org.elasticsearch.action.search;
 
+import org.elasticsearch.ElasticsearchIllegalArgumentException;
 import org.elasticsearch.action.ActionRequest;
 import org.elasticsearch.action.ActionRequestValidationException;
+import org.elasticsearch.common.bytes.BytesArray;
+import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
 
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.List;
 
-import static com.google.common.collect.Lists.newArrayList;
-import static org.elasticsearch.action.ValidateActions.addValidationError;
+import static org.elasticsearch.action.ValidateActions.*;
 
 /**
  */
 public class ClearScrollRequest extends ActionRequest<ClearScrollRequest> {
 
-    private List<String> scrollIds;
+    private BytesReference source;
 
-    public List<String> getScrollIds() {
-        return scrollIds;
+    public void setScrollIds(String... scrollIds) {
+        if (scrollIds != null) {
+            this.setScrollIds(Arrays.asList(scrollIds));
+        }
     }
 
     public void setScrollIds(List<String> scrollIds) {
-        this.scrollIds = scrollIds;
+        if (scrollIds != null && scrollIds.isEmpty() == false) {
+            try {
+                XContentBuilder builder = XContentFactory.jsonBuilder();
+                builder.startObject();
+                builder.startArray("scroll_id");
+                for (String scrollId : scrollIds) {
+                    builder.value(scrollId);
+                }
+                builder.endArray();
+                builder.endObject();
+                this.source(builder.string());
+            } catch (Exception e) {
+                throw new ElasticsearchIllegalArgumentException("Failed to build source");
+            }
+        }
     }
 
-    public void addScrollId(String scrollId) {
-        if (scrollIds == null) {
-            scrollIds = newArrayList();
-        }
-        scrollIds.add(scrollId);
+    public void scrollIds(List<String> scrollIds) {
+        setScrollIds(scrollIds);
     }
 
-    public List<String> scrollIds() {
-        return scrollIds;
+    public ClearScrollRequest source(String source) {
+        this.source = new BytesArray(source);
+        return this;
     }
 
-    public void scrollIds(List<String> scrollIds) {
-        this.scrollIds = scrollIds;
+    public ClearScrollRequest source(BytesReference source) {
+        this.source = source;
+        return this;
+    }
+
+    public BytesReference source() {
+        return source;
     }
 
     @Override
     public ActionRequestValidationException validate() {
         ActionRequestValidationException validationException = null;
-        if (scrollIds == null || scrollIds.isEmpty()) {
+        if (source == null) {
             validationException = addValidationError("no scroll ids specified", validationException);
         }
         return validationException;
@@ -72,17 +95,13 @@ public class ClearScrollRequest extends ActionRequest<ClearScrollRequest> {
     @Override
     public void readFrom(StreamInput in) throws IOException {
         super.readFrom(in);
-        scrollIds = Arrays.asList(in.readStringArray());
+        source = in.readBytesReference();
     }
 
     @Override
     public void writeTo(StreamOutput out) throws IOException {
         super.writeTo(out);
-        if (scrollIds == null) {
-            out.writeVInt(0);
-        } else {
-            out.writeStringArray(scrollIds.toArray(new String[scrollIds.size()]));
-        }
+        out.writeBytesReference(source);
     }
 
 }

+ 22 - 2
src/main/java/org/elasticsearch/action/search/ClearScrollRequestBuilder.java

@@ -19,9 +19,11 @@
 
 package org.elasticsearch.action.search;
 
+import com.google.common.collect.Lists;
 import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.ActionRequestBuilder;
 import org.elasticsearch.client.Client;
+import org.elasticsearch.common.bytes.BytesReference;
 
 import java.util.List;
 
@@ -29,22 +31,40 @@ import java.util.List;
  */
 public class ClearScrollRequestBuilder extends ActionRequestBuilder<ClearScrollRequest, ClearScrollResponse, ClearScrollRequestBuilder, Client> {
 
+    private List<String> scrollIds;
+
     public ClearScrollRequestBuilder(Client client) {
         super(client, new ClearScrollRequest());
     }
 
     public ClearScrollRequestBuilder setScrollIds(List<String> cursorIds) {
-        request.setScrollIds(cursorIds);
+        scrollIds = cursorIds;
         return this;
     }
 
     public ClearScrollRequestBuilder addScrollId(String cursorId) {
-        request.addScrollId(cursorId);
+        if (this.scrollIds == null) {
+            this.scrollIds = Lists.newArrayList();
+        }
+        this.scrollIds.add(cursorId);
+        return this;
+    }
+
+    public ClearScrollRequestBuilder setSource(String source) {
+        request.source(source);
+        return this;
+    }
+
+    public ClearScrollRequestBuilder setSource(BytesReference source, boolean unsafe) {
+        request.source(source);
         return this;
     }
 
     @Override
     protected void doExecute(ActionListener<ClearScrollResponse> listener) {
+        if (scrollIds != null && scrollIds.isEmpty() == false) {
+            request.setScrollIds(this.scrollIds);
+        }
         client.clearScroll(request, listener);
     }
 }

+ 47 - 9
src/main/java/org/elasticsearch/action/search/SearchScrollRequest.java

@@ -21,6 +21,9 @@ package org.elasticsearch.action.search;
 
 import org.elasticsearch.action.ActionRequest;
 import org.elasticsearch.action.ActionRequestValidationException;
+import org.elasticsearch.client.Requests;
+import org.elasticsearch.common.bytes.BytesArray;
+import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.unit.TimeValue;
@@ -28,8 +31,8 @@ import org.elasticsearch.search.Scroll;
 
 import java.io.IOException;
 
-import static org.elasticsearch.action.ValidateActions.addValidationError;
-import static org.elasticsearch.search.Scroll.readScroll;
+import static org.elasticsearch.action.ValidateActions.*;
+import static org.elasticsearch.search.Scroll.*;
 
 /**
  *
@@ -39,6 +42,8 @@ public class SearchScrollRequest extends ActionRequest<SearchScrollRequest> {
     private String scrollId;
     private Scroll scroll;
 
+    private BytesReference source;
+
     public SearchScrollRequest() {
     }
 
@@ -46,10 +51,14 @@ public class SearchScrollRequest extends ActionRequest<SearchScrollRequest> {
         this.scrollId = scrollId;
     }
 
+    public void setScrollId(String scrollId) {
+        this.scrollId = scrollId;
+    }
+
     @Override
     public ActionRequestValidationException validate() {
         ActionRequestValidationException validationException = null;
-        if (scrollId == null) {
+        if (scrollId == null && source == null) {
             validationException = addValidationError("scrollId is missing", validationException);
         }
         return validationException;
@@ -62,7 +71,7 @@ public class SearchScrollRequest extends ActionRequest<SearchScrollRequest> {
         return scrollId;
     }
 
-    public SearchScrollRequest scrollId(String scrollId) {
+    protected SearchScrollRequest scrollId(String scrollId) {
         this.scrollId = scrollId;
         return this;
     }
@@ -77,7 +86,7 @@ public class SearchScrollRequest extends ActionRequest<SearchScrollRequest> {
     /**
      * If set, will enable scrolling of the search request.
      */
-    public SearchScrollRequest scroll(Scroll scroll) {
+    protected SearchScrollRequest scroll(Scroll scroll) {
         this.scroll = scroll;
         return this;
     }
@@ -85,35 +94,64 @@ public class SearchScrollRequest extends ActionRequest<SearchScrollRequest> {
     /**
      * If set, will enable scrolling of the search request for the specified timeout.
      */
-    public SearchScrollRequest scroll(TimeValue keepAlive) {
+    protected SearchScrollRequest scroll(TimeValue keepAlive) {
         return scroll(new Scroll(keepAlive));
     }
 
     /**
      * If set, will enable scrolling of the search request for the specified timeout.
      */
-    public SearchScrollRequest scroll(String keepAlive) {
+    protected SearchScrollRequest scroll(String keepAlive) {
         return scroll(new Scroll(TimeValue.parseTimeValue(keepAlive, null)));
     }
 
+    public SearchScrollRequest source(String source) {
+        this.source = new BytesArray(source);
+        return this;
+    }
+
+    public SearchScrollRequest source(BytesReference source) {
+        this.source = source;
+        return this;
+    }
+
+    public SearchScrollRequest source(SearchScrollSourceBuilder sourceBuilder) {
+        this.source = sourceBuilder.buildAsBytes(Requests.CONTENT_TYPE);
+        return this;
+    }
+
+    public BytesReference source() {
+        return source;
+    }
+
     @Override
     public void readFrom(StreamInput in) throws IOException {
         super.readFrom(in);
-        scrollId = in.readString();
+        if (in.readBoolean()) {
+            scrollId = in.readString();
+        }
         if (in.readBoolean()) {
             scroll = readScroll(in);
         }
+        source = in.readBytesReference();
     }
 
     @Override
     public void writeTo(StreamOutput out) throws IOException {
         super.writeTo(out);
-        out.writeString(scrollId);
+        if (scrollId == null) {
+            out.writeBoolean(false);
+        } else {
+            out.writeBoolean(true);
+            out.writeString(scrollId);
+        }
         if (scroll == null) {
             out.writeBoolean(false);
         } else {
             out.writeBoolean(true);
             scroll.writeTo(out);
         }
+        out.writeBytesReference(source);
     }
+
 }

+ 33 - 5
src/main/java/org/elasticsearch/action/search/SearchScrollRequestBuilder.java

@@ -22,6 +22,7 @@ package org.elasticsearch.action.search;
 import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.ActionRequestBuilder;
 import org.elasticsearch.client.Client;
+import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.unit.TimeValue;
 import org.elasticsearch.search.Scroll;
 
@@ -30,12 +31,25 @@ import org.elasticsearch.search.Scroll;
  */
 public class SearchScrollRequestBuilder extends ActionRequestBuilder<SearchScrollRequest, SearchResponse, SearchScrollRequestBuilder, Client> {
 
+    private SearchScrollSourceBuilder sourceBuilder;
+
     public SearchScrollRequestBuilder(Client client) {
         super(client, new SearchScrollRequest());
     }
 
     public SearchScrollRequestBuilder(Client client, String scrollId) {
-        super(client, new SearchScrollRequest(scrollId));
+        super(client, new SearchScrollRequest());
+        if (sourceBuilder == null) {
+            sourceBuilder = new SearchScrollSourceBuilder();
+        }
+        sourceBuilder.scrollId(scrollId);
+    }
+
+    private SearchScrollSourceBuilder sourceBuilder() {
+        if (sourceBuilder == null) {
+            sourceBuilder = new SearchScrollSourceBuilder();
+        }
+        return sourceBuilder;
     }
 
     /**
@@ -50,7 +64,7 @@ public class SearchScrollRequestBuilder extends ActionRequestBuilder<SearchScrol
      * The scroll id to use to continue scrolling.
      */
     public SearchScrollRequestBuilder setScrollId(String scrollId) {
-        request.scrollId(scrollId);
+        sourceBuilder().scrollId(scrollId);
         return this;
     }
 
@@ -58,7 +72,7 @@ public class SearchScrollRequestBuilder extends ActionRequestBuilder<SearchScrol
      * If set, will enable scrolling of the search request.
      */
     public SearchScrollRequestBuilder setScroll(Scroll scroll) {
-        request.scroll(scroll);
+        sourceBuilder().scroll(scroll);
         return this;
     }
 
@@ -66,7 +80,7 @@ public class SearchScrollRequestBuilder extends ActionRequestBuilder<SearchScrol
      * If set, will enable scrolling of the search request for the specified timeout.
      */
     public SearchScrollRequestBuilder setScroll(TimeValue keepAlive) {
-        request.scroll(keepAlive);
+        sourceBuilder().scroll(keepAlive);
         return this;
     }
 
@@ -74,12 +88,26 @@ public class SearchScrollRequestBuilder extends ActionRequestBuilder<SearchScrol
      * If set, will enable scrolling of the search request for the specified timeout.
      */
     public SearchScrollRequestBuilder setScroll(String keepAlive) {
-        request.scroll(keepAlive);
+        sourceBuilder().scroll(keepAlive);
+        return this;
+    }
+
+
+    public SearchScrollRequestBuilder setSource(String source) {
+        request.source(source);
+        return this;
+    }
+
+    public SearchScrollRequestBuilder setSource(BytesReference source) {
+        request.source(source);
         return this;
     }
 
     @Override
     protected void doExecute(ActionListener<SearchResponse> listener) {
+        if (sourceBuilder != null) {
+            request.source(sourceBuilder);
+        }
         client.searchScroll(request, listener);
     }
 }

+ 94 - 0
src/main/java/org/elasticsearch/action/search/SearchScrollSourceBuilder.java

@@ -0,0 +1,94 @@
+/*
+ * 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.action.search;
+
+import org.elasticsearch.common.bytes.BytesReference;
+import org.elasticsearch.common.unit.TimeValue;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.common.xcontent.XContentType;
+import org.elasticsearch.search.Scroll;
+import org.elasticsearch.search.builder.SearchSourceBuilderException;
+
+import java.io.IOException;
+
+/**
+ * Builder to create the search scroll builder
+ */
+public class SearchScrollSourceBuilder implements ToXContent{
+
+    private String scrollId;
+    private String scroll;
+
+    public SearchScrollSourceBuilder scrollId(String scrollId) {
+        this.scrollId = scrollId;
+        return this;
+    }
+
+    /**
+     * If set, will enable scrolling of the search request.
+     */
+    public SearchScrollSourceBuilder scroll(Scroll scroll) {
+        if (scroll != null) {
+            this.scroll = scroll.keepAlive().toString();
+        }
+        return this;
+    }
+
+    /**
+     * If set, will enable scrolling of the search request for the specified timeout.
+     */
+    public SearchScrollSourceBuilder scroll(TimeValue keepAlive) {
+        if (keepAlive != null) {
+            this.scroll = keepAlive.toString();
+        }
+        return this;
+    }
+
+    /**
+     * If set, will enable scrolling of the search request for the specified timeout.
+     */
+    public SearchScrollSourceBuilder scroll(String keepAlive) {
+        this.scroll = keepAlive;
+        return this;
+    }
+
+    @Override
+    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+        builder.startObject();
+        builder.field("scroll_id", this.scrollId);
+        if (scroll != null) {
+            builder.field("scroll", this.scroll);
+        }
+        builder.endObject();
+        return builder;
+    }
+
+    public BytesReference buildAsBytes(XContentType contentType) throws SearchSourceBuilderException {
+        try {
+            XContentBuilder builder = XContentFactory.contentBuilder(contentType);
+            toXContent(builder, ToXContent.EMPTY_PARAMS);
+            return builder.bytes();
+        } catch (Exception e) {
+            throw new SearchSourceBuilderException("Failed to build search source", e);
+        }
+    }
+}

+ 30 - 6
src/main/java/org/elasticsearch/action/search/TransportClearScrollAction.java

@@ -19,30 +19,32 @@
 
 package org.elasticsearch.action.search;
 
+import com.google.common.collect.Lists;
 import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.support.ActionFilters;
 import org.elasticsearch.action.support.HandledTransportAction;
-import org.elasticsearch.action.support.TransportAction;
 import org.elasticsearch.cluster.ClusterService;
 import org.elasticsearch.cluster.ClusterState;
 import org.elasticsearch.cluster.node.DiscoveryNode;
 import org.elasticsearch.cluster.node.DiscoveryNodes;
+import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.collect.Tuple;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.util.concurrent.CountDown;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.search.action.SearchServiceTransportAction;
 import org.elasticsearch.threadpool.ThreadPool;
-import org.elasticsearch.transport.BaseTransportRequestHandler;
-import org.elasticsearch.transport.TransportChannel;
 import org.elasticsearch.transport.TransportService;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
-import static org.elasticsearch.action.search.type.TransportSearchHelper.parseScrollId;
+import static org.elasticsearch.action.search.type.TransportSearchHelper.*;
 
 /**
  */
@@ -63,6 +65,27 @@ public class TransportClearScrollAction extends HandledTransportAction<ClearScro
         new Async(request, listener, clusterService.state()).run();
     }
 
+    private List<String> parseSource(ClearScrollRequest request) {
+        List<String> scrollIds = Lists.newArrayList();
+        if (request.source() != null) {
+            XContentType contentType = XContentFactory.xContentType(request.source());
+            if (contentType == null) {
+                // For backward compatibility
+                scrollIds = (Arrays.asList(splitScrollIds(request.source().toUtf8())));
+            } else {
+                scrollIds = parseScrollIdListFromSource(request.source());
+            }
+        }
+        return scrollIds;
+    }
+
+    private static String[] splitScrollIds(String scrollIds) {
+        if (scrollIds == null) {
+            return Strings.EMPTY_ARRAY;
+        }
+        return Strings.splitStringByCommaToArray(scrollIds);
+    }
+
     @Override
     public ClearScrollRequest newRequestInstance() {
         return new ClearScrollRequest();
@@ -81,10 +104,11 @@ public class TransportClearScrollAction extends HandledTransportAction<ClearScro
         private Async(ClearScrollRequest request, ActionListener<ClearScrollResponse> listener, ClusterState clusterState) {
             int expectedOps = 0;
             this.nodes = clusterState.nodes();
-            if (request.getScrollIds().size() == 1 && "_all".equals(request.getScrollIds().get(0))) {
+            List<String> scrollIds = parseSource(request);
+            if (scrollIds.size() == 1 && "_all".equals(scrollIds.get(0))) {
                 expectedOps = nodes.size();
             } else {
-                for (String parsedScrollId : request.getScrollIds()) {
+                for (String parsedScrollId : scrollIds) {
                     Tuple<String, Long>[] context = parseScrollId(parsedScrollId).getContext();
                     expectedOps += context.length;
                     this.contexts.add(context);

+ 20 - 4
src/main/java/org/elasticsearch/action/search/TransportSearchScrollAction.java

@@ -27,16 +27,15 @@ import org.elasticsearch.action.search.type.TransportSearchScrollQueryThenFetchA
 import org.elasticsearch.action.search.type.TransportSearchScrollScanAction;
 import org.elasticsearch.action.support.ActionFilters;
 import org.elasticsearch.action.support.HandledTransportAction;
-import org.elasticsearch.action.support.TransportAction;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.threadpool.ThreadPool;
-import org.elasticsearch.transport.BaseTransportRequestHandler;
-import org.elasticsearch.transport.TransportChannel;
 import org.elasticsearch.transport.TransportService;
 
 import static org.elasticsearch.action.search.type.ParsedScrollId.*;
-import static org.elasticsearch.action.search.type.TransportSearchHelper.parseScrollId;
+import static org.elasticsearch.action.search.type.TransportSearchHelper.*;
 
 /**
  *
@@ -63,6 +62,7 @@ public class TransportSearchScrollAction extends HandledTransportAction<SearchSc
     @Override
     protected void doExecute(SearchScrollRequest request, ActionListener<SearchResponse> listener) {
         try {
+            parseSource(request);
             ParsedScrollId scrollId = parseScrollId(request.scrollId());
             if (scrollId.getType().equals(QUERY_THEN_FETCH_TYPE)) {
                 queryThenFetchAction.execute(request, scrollId, listener);
@@ -78,6 +78,22 @@ public class TransportSearchScrollAction extends HandledTransportAction<SearchSc
         }
     }
 
+    /**
+     * Note: do the parsing here before we delegate to any of the actions
+     */
+    private void parseSource(SearchScrollRequest request) {
+        if (request.source() != null) {
+            XContentType contentType = XContentFactory.xContentType(request.source());
+            if (contentType == null) {
+                // For backward compatibility
+                request.setScrollId(request.source().toUtf8());
+            } else {
+                request.setScrollId(parseScrollIdFromSource(request.source()));
+                request.scroll(parseScrollFromSource(request.source()));
+            }
+        }
+    }
+
     @Override
     public SearchScrollRequest newRequestInstance() {
         return new SearchScrollRequest();

+ 61 - 0
src/main/java/org/elasticsearch/action/search/type/TransportSearchHelper.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.action.search.type;
 
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.CharsRefBuilder;
@@ -32,13 +33,19 @@ import org.elasticsearch.cluster.routing.ShardRouting;
 import org.elasticsearch.common.Base64;
 import org.elasticsearch.common.Nullable;
 import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.collect.Tuple;
+import org.elasticsearch.common.unit.TimeValue;
 import org.elasticsearch.common.util.concurrent.AtomicArray;
+import org.elasticsearch.common.xcontent.XContentHelper;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.Scroll;
 import org.elasticsearch.search.SearchPhaseResult;
 import org.elasticsearch.search.internal.InternalScrollSearchRequest;
 import org.elasticsearch.search.internal.ShardSearchTransportRequest;
 
 import java.io.IOException;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -133,4 +140,58 @@ public abstract class TransportSearchHelper {
 
     }
 
+    public static String parseScrollIdFromSource(BytesReference source) throws ElasticsearchIllegalArgumentException {
+        List<String> scrollIds = parseScrollIdListFromSource(source);
+        return scrollIds.isEmpty() ? null : scrollIds.get(0);
+    }
+
+    public static List<String> parseScrollIdListFromSource(BytesReference source) throws ElasticsearchIllegalArgumentException {
+        List<String> scrollIds = Lists.newArrayList();
+        try (XContentParser parser = XContentHelper.createParser(source)) {
+            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 (token == XContentParser.Token.VALUE_STRING) {
+                    if ("scroll_id".equals(currentFieldName)) {
+                        scrollIds.add(parser.text());
+                    }
+                } else if (token == XContentParser.Token.START_ARRAY) {
+                    if ("scroll_id".equals(currentFieldName)) {
+                        while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
+                            if (!token.isValue()) {
+                                throw new ElasticsearchIllegalArgumentException("scroll_id array element should only contain scroll_id");
+                            }
+                            scrollIds.add(parser.text());
+                        }
+                    }
+                }
+            }
+        } catch (IOException e) {
+            throw new ElasticsearchIllegalArgumentException("Failed to parse request body", e);
+        }
+        return scrollIds;
+    }
+
+    public static Scroll parseScrollFromSource(BytesReference source) throws ElasticsearchIllegalArgumentException {
+        Scroll scroll = null;
+        try (XContentParser parser = XContentHelper.createParser(source)) {
+            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 (token == XContentParser.Token.VALUE_STRING) {
+                    if ("scroll".equals(currentFieldName)) {
+                        scroll = new Scroll(TimeValue.parseTimeValue(parser.text(), null));
+                    }
+                }
+            }
+        } catch (IOException e) {
+            throw new ElasticsearchIllegalArgumentException("Failed to parse request body", e);
+        }
+        return scroll;
+    }
+
 }

+ 23 - 18
src/main/java/org/elasticsearch/rest/action/admin/indices/analyze/RestAnalyzeAction.java

@@ -18,17 +18,20 @@
  */
 package org.elasticsearch.rest.action.admin.indices.analyze;
 
-import org.elasticsearch.ElasticsearchIllegalArgumentException;
 import org.elasticsearch.action.admin.indices.analyze.AnalyzeRequest;
 import org.elasticsearch.action.admin.indices.analyze.AnalyzeResponse;
+import org.elasticsearch.action.admin.indices.analyze.AnalyzeSourceBuilder;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.rest.*;
+import org.elasticsearch.rest.BaseRestHandler;
+import org.elasticsearch.rest.RestChannel;
+import org.elasticsearch.rest.RestController;
+import org.elasticsearch.rest.RestRequest;
+import org.elasticsearch.rest.action.support.RestActions;
 import org.elasticsearch.rest.action.support.RestToXContentListener;
 
-import static org.elasticsearch.rest.RestRequest.Method.GET;
-import static org.elasticsearch.rest.RestRequest.Method.POST;
+import static org.elasticsearch.rest.RestRequest.Method.*;
 
 /**
  *
@@ -46,22 +49,24 @@ public class RestAnalyzeAction extends BaseRestHandler {
 
     @Override
     public void handleRequest(final RestRequest request, final RestChannel channel, final Client client) {
-        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"));
+        analyzeRequest.listenerThreaded(false);
+
+        AnalyzeSourceBuilder sourceBuilder = new AnalyzeSourceBuilder();
+        sourceBuilder.setAnalyzer(request.param("analyzer"));
+        sourceBuilder.setTokenizer(request.param("tokenizer"));
+        sourceBuilder.setText(request.param("text"));
+        sourceBuilder.setCharFilters(request.paramAsStringArray("char_filters", null));
+        sourceBuilder.setTokenFilters(request.paramAsStringArray("token_filters", request.paramAsStringArray("filters", null)));
+        sourceBuilder.setField(request.param("field"));
+        sourceBuilder.setPreferLocal(request.paramAsBoolean("prefer_local", analyzeRequest.preferLocalShard()));
+
+        if (request.hasContent()) {
+            analyzeRequest.source(RestActions.getRestContent(request), request.contentUnsafe());
+        } else {
+            analyzeRequest.source(sourceBuilder);
         }
 
-        AnalyzeRequest analyzeRequest = new AnalyzeRequest(request.param("index"), text);
-        analyzeRequest.listenerThreaded(false);
-        analyzeRequest.preferLocal(request.paramAsBoolean("prefer_local", analyzeRequest.preferLocalShard()));
-        analyzeRequest.analyzer(request.param("analyzer"));
-        analyzeRequest.field(request.param("field"));
-        analyzeRequest.tokenizer(request.param("tokenizer"));
-        analyzeRequest.tokenFilters(request.paramAsStringArray("token_filters", request.paramAsStringArray("filters", analyzeRequest.tokenFilters())));
-        analyzeRequest.charFilters(request.paramAsStringArray("char_filters", analyzeRequest.charFilters()));
         client.admin().indices().analyze(analyzeRequest, new RestToXContentListener<AnalyzeResponse>(channel));
     }
 }

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

@@ -25,13 +25,14 @@ import org.elasticsearch.client.Client;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.rest.*;
+import org.elasticsearch.rest.BaseRestHandler;
+import org.elasticsearch.rest.RestChannel;
+import org.elasticsearch.rest.RestController;
+import org.elasticsearch.rest.RestRequest;
 import org.elasticsearch.rest.action.support.RestActions;
 import org.elasticsearch.rest.action.support.RestStatusToXContentListener;
 
-import java.util.Arrays;
-
-import static org.elasticsearch.rest.RestRequest.Method.DELETE;
+import static org.elasticsearch.rest.RestRequest.Method.*;
 
 /**
  */
@@ -47,17 +48,16 @@ public class RestClearScrollAction extends BaseRestHandler {
 
     @Override
     public void handleRequest(final RestRequest request, final RestChannel channel, final Client client) {
-        String scrollIds = request.param("scroll_id");
-        if (scrollIds == null) {
-            scrollIds = RestActions.getRestContent(request).toUtf8();
-        }
 
         ClearScrollRequest clearRequest = new ClearScrollRequest();
-        clearRequest.setScrollIds(Arrays.asList(splitScrollIds(scrollIds)));
+        clearRequest.setScrollIds(splitScrollIds(request.param("scroll_id")));
+        if (request.hasContent()) {
+            clearRequest.source(RestActions.getRestContent(request));
+        }
         client.clearScroll(clearRequest, new RestStatusToXContentListener<ClearScrollResponse>(channel));
     }
 
-    public static String[] splitScrollIds(String scrollIds) {
+    private static String[] splitScrollIds(String scrollIds) {
         if (scrollIds == null) {
             return Strings.EMPTY_ARRAY;
         }

+ 15 - 13
src/main/java/org/elasticsearch/rest/action/search/RestSearchScrollAction.java

@@ -21,17 +21,18 @@ package org.elasticsearch.rest.action.search;
 
 import org.elasticsearch.action.search.SearchResponse;
 import org.elasticsearch.action.search.SearchScrollRequest;
+import org.elasticsearch.action.search.SearchScrollSourceBuilder;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.rest.*;
+import org.elasticsearch.rest.BaseRestHandler;
+import org.elasticsearch.rest.RestChannel;
+import org.elasticsearch.rest.RestController;
+import org.elasticsearch.rest.RestRequest;
 import org.elasticsearch.rest.action.support.RestActions;
 import org.elasticsearch.rest.action.support.RestStatusToXContentListener;
-import org.elasticsearch.search.Scroll;
 
-import static org.elasticsearch.common.unit.TimeValue.parseTimeValue;
-import static org.elasticsearch.rest.RestRequest.Method.GET;
-import static org.elasticsearch.rest.RestRequest.Method.POST;
+import static org.elasticsearch.rest.RestRequest.Method.*;
 
 /**
  *
@@ -50,15 +51,16 @@ public class RestSearchScrollAction extends BaseRestHandler {
 
     @Override
     public void handleRequest(final RestRequest request, final RestChannel channel, final Client client) {
-        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);
-        String scroll = request.param("scroll");
-        if (scroll != null) {
-            searchScrollRequest.scroll(new Scroll(parseTimeValue(scroll, null)));
+        SearchScrollSourceBuilder sourceBuilder = new SearchScrollSourceBuilder();
+        sourceBuilder.scrollId(request.param("scroll_id"));
+        sourceBuilder.scroll(request.param("scroll"));
+
+        if (request.hasContent()) {
+            searchScrollRequest.source(RestActions.getRestContent(request));
+        } else {
+            searchScrollRequest.source(sourceBuilder);
         }
 
         client.searchScroll(searchScrollRequest, new RestStatusToXContentListener<SearchResponse>(channel));

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

@@ -22,6 +22,7 @@ package org.elasticsearch.action;
 import org.elasticsearch.action.admin.indices.alias.Alias;
 import org.elasticsearch.action.admin.indices.analyze.AnalyzeAction;
 import org.elasticsearch.action.admin.indices.analyze.AnalyzeRequest;
+import org.elasticsearch.action.admin.indices.analyze.AnalyzeSourceBuilder;
 import org.elasticsearch.action.admin.indices.cache.clear.ClearIndicesCacheAction;
 import org.elasticsearch.action.admin.indices.cache.clear.ClearIndicesCacheRequest;
 import org.elasticsearch.action.admin.indices.close.CloseIndexAction;
@@ -178,7 +179,7 @@ public class IndicesRequestTests extends ElasticsearchIntegrationTest {
         String analyzeShardAction = AnalyzeAction.NAME + "[s]";
         interceptTransportActions(analyzeShardAction);
 
-        AnalyzeRequest analyzeRequest = new AnalyzeRequest(randomIndexOrAlias(), "text");
+        AnalyzeRequest analyzeRequest = new AnalyzeRequest(randomIndexOrAlias()).source(new AnalyzeSourceBuilder().setText("text"));
         internalCluster().clientNodeClient().admin().indices().analyze(analyzeRequest).actionGet();
 
         clearInterceptedActions();

+ 28 - 2
src/test/java/org/elasticsearch/indices/analyze/AnalyzeActionTests.java

@@ -23,13 +23,14 @@ import org.elasticsearch.ElasticsearchIllegalArgumentException;
 import org.elasticsearch.action.admin.indices.alias.Alias;
 import org.elasticsearch.action.admin.indices.analyze.AnalyzeRequestBuilder;
 import org.elasticsearch.action.admin.indices.analyze.AnalyzeResponse;
+import org.elasticsearch.action.admin.indices.analyze.AnalyzeSourceBuilder;
 import org.elasticsearch.test.ElasticsearchIntegrationTest;
 import org.junit.Test;
 
 import java.io.IOException;
 
-import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
-import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
+import static org.elasticsearch.common.settings.ImmutableSettings.*;
+import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.*;
 import static org.hamcrest.Matchers.*;
 
 /**
@@ -191,4 +192,29 @@ public class AnalyzeActionTests extends ElasticsearchIntegrationTest {
     private static String indexOrAlias() {
         return randomBoolean() ? "test" : "alias";
     }
+
+    @Test
+    public void testSourceInAnalyzeRequest() {
+        AnalyzeRequestBuilder builder = new AnalyzeRequestBuilder(client().admin().indices());
+        AnalyzeSourceBuilder sourceBuilder = new AnalyzeSourceBuilder();
+        sourceBuilder.setText("THIS IS A TEST").setTokenizer("keyword").setTokenFilters("lowercase");
+
+        builder.setSource(sourceBuilder);
+        AnalyzeResponse analyzeResponse = builder.get();
+        assertThat(analyzeResponse.getTokens().size(), equalTo(1));
+        assertThat(analyzeResponse.getTokens().get(0).getTerm(), equalTo("this is a test"));
+    }
+    @Test
+    public void testParseAnalyzeRequestThrowsException() throws Exception {
+        AnalyzeRequestBuilder builder = new AnalyzeRequestBuilder(client().admin().indices());
+        builder.setSource("{ \"invalid_json\" ");
+
+        try {
+            builder.get();
+            fail("expected parseContent failure");
+        } catch (Exception e) {
+            assertThat(e, instanceOf(ElasticsearchIllegalArgumentException.class));
+            assertThat(e.getMessage(), equalTo("Failed to parse request body"));
+        }
+    }
 }

+ 39 - 6
src/test/java/org/elasticsearch/search/scroll/SearchScrollTests.java

@@ -20,10 +20,7 @@
 package org.elasticsearch.search.scroll;
 
 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.common.Priority;
 import org.elasticsearch.common.settings.ImmutableSettings;
@@ -460,8 +457,8 @@ public class SearchScrollTests extends ElasticsearchIntegrationTest {
     @Test
     public void testStringSortMissingAscTerminates() throws Exception {
         assertAcked(prepareCreate("test")
-                .setSettings(ImmutableSettings.settingsBuilder().put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1).put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0))
-                .addMapping("test", "no_field", "type=string", "some_field", "type=string"));
+            .setSettings(ImmutableSettings.settingsBuilder().put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1).put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0))
+            .addMapping("test", "no_field", "type=string", "some_field", "type=string"));
         client().prepareIndex("test", "test", "1").setSource("some_field", "test").get();
         refresh();
 
@@ -490,4 +487,40 @@ public class SearchScrollTests extends ElasticsearchIntegrationTest {
         assertHitCount(response, 1);
         assertThat(response.getHits().getHits().length, equalTo(0));
     }
+
+    @Test
+    public void testParseClearScrollRequestThrowsException() throws Exception {
+        client().prepareIndex("index", "type", "1").setSource("field", "value").execute().get();
+        refresh();
+
+        SearchResponse searchResponse = client().prepareSearch("index").setSize(1).setScroll("1m").get();
+        assertThat(searchResponse.getScrollId(), is(notNullValue()));
+
+        ClearScrollRequestBuilder builder = new ClearScrollRequestBuilder(client());
+        builder.setSource("{\"scroll_id\" : [ \"json_scroll_id\" ] ");
+
+        try {
+            builder.get();
+            fail("expected parseContent failure");
+        } catch (Exception e) {
+            assertThat(e, instanceOf(ElasticsearchIllegalArgumentException.class));
+            assertThat(e.getMessage(), equalTo("Failed to parse request body"));
+        }
+    }
+    @Test
+    public void testParseSearchScrollRequestThrowsException() throws Exception {
+        client().prepareIndex("index", "type", "1").setSource("field", "value").execute().get();
+        refresh();
+
+        SearchScrollRequestBuilder builder = new SearchScrollRequestBuilder(client());
+        builder.setSource("{\"scroll_id\" : [ \"json_scroll_id\" ] ");
+        try {
+            builder.get();
+            fail("expected parseContent failure");
+        } catch (Exception e) {
+            assertThat(e, instanceOf(ElasticsearchIllegalArgumentException.class));
+            assertThat(e.getMessage(), equalTo("Failed to parse request body"));
+        }
+    }
+
 }