Bläddra i källkod

Refactor control flow in TransportAnalyzeAction (#42801)

The control flow in TransportAnalyzeAction is currently spread across two large
methods, and is quite difficult to follow. This commit tidies things up a bit, to make
it clearer when we use pre-defined analyzers and when we use custom built ones.
Alan Woodward 6 år sedan
förälder
incheckning
92bbcd8c2d

+ 12 - 0
server/src/main/java/org/elasticsearch/action/admin/indices/analyze/AnalyzeAction.java

@@ -282,6 +282,18 @@ public class AnalyzeAction extends Action<AnalyzeAction.Response> {
                 validationException
                     = addValidationError("tokenizer/analyze should be null if normalizer is specified", validationException);
             }
+            if (analyzer != null && (tokenizer != null || charFilters.isEmpty() == false || tokenFilters.isEmpty() == false)) {
+                validationException
+                    = addValidationError("cannot define extra components on a named analyzer", validationException);
+            }
+            if (normalizer != null && (tokenizer != null || charFilters.isEmpty() == false || tokenFilters.isEmpty() == false)) {
+                validationException
+                    = addValidationError("cannot define extra components on a named normalizer", validationException);
+            }
+            if (field != null && (tokenizer != null || charFilters.isEmpty() == false || tokenFilters.isEmpty() == false)) {
+                validationException
+                    = addValidationError("cannot define extra components on a field-specific analyzer", validationException);
+            }
             return validationException;
         }
 

+ 97 - 101
server/src/main/java/org/elasticsearch/action/admin/indices/analyze/TransportAnalyzeAction.java

@@ -124,72 +124,92 @@ public class TransportAnalyzeAction extends TransportSingleShardAction<AnalyzeAc
     }
 
     @Override
-    protected AnalyzeAction.Response shardOperation(AnalyzeAction.Request request, ShardId shardId) {
-        try {
-            final IndexService indexService;
-            if (shardId != null) {
-                indexService = indicesService.indexServiceSafe(shardId.getIndex());
-            } else {
-                indexService = null;
-            }
-            String field = null;
-            Analyzer analyzer = null;
-            if (request.field() != null) {
-                if (indexService == null) {
-                    throw new IllegalArgumentException(
-                        "No index provided, and trying to analyzer based on a specific field which requires the index parameter");
-                }
-                MappedFieldType fieldType = indexService.mapperService().fullName(request.field());
-                if (fieldType != null) {
-                    if (fieldType.tokenized() || fieldType instanceof KeywordFieldMapper.KeywordFieldType) {
-                        analyzer = fieldType.indexAnalyzer();
-                    } else {
-                        throw new IllegalArgumentException("Can't process field [" + request.field() +
-                            "], Analysis requests are only supported on tokenized fields");
-                    }
-                    field = fieldType.name();
-                }
-            }
-            if (field == null) {
-                /**
-                 * TODO: _all is disabled by default and index.query.default_field can define multiple fields or patterns so we should
-                 * probably makes the field name mandatory in analyze query.
-                 **/
-                if (indexService != null) {
-                    field = indexService.getIndexSettings().getDefaultFields().get(0);
-                }
+    protected AnalyzeAction.Response shardOperation(AnalyzeAction.Request request, ShardId shardId) throws IOException {
+        final IndexService indexService = getIndexService(shardId);
+        final int maxTokenCount = indexService == null ?
+            IndexSettings.MAX_TOKEN_COUNT_SETTING.get(settings) : indexService.getIndexSettings().getMaxTokenCount();
+
+        return analyze(request, indicesService.getAnalysis(), environment, indexService, maxTokenCount);
+    }
+
+    public static AnalyzeAction.Response analyze(AnalyzeAction.Request request, AnalysisRegistry analysisRegistry,
+                                          Environment environment, IndexService indexService, int maxTokenCount) throws IOException {
+
+        IndexAnalyzers indexAnalyzers = indexService == null ? null : indexService.getIndexAnalyzers();
+
+        // First, we check to see if the request requires a custom analyzer.  If so, then we
+        // need to build it and then close it after use.
+        try (Analyzer analyzer = buildCustomAnalyzer(request, analysisRegistry, indexAnalyzers, environment)) {
+            if (analyzer != null) {
+                return analyze(request, analyzer, maxTokenCount);
             }
-            final AnalysisRegistry analysisRegistry = indicesService.getAnalysis();
-            final int maxTokenCount = indexService == null ?
-                IndexSettings.MAX_TOKEN_COUNT_SETTING.get(settings) : indexService.getIndexSettings().getMaxTokenCount();
-            return analyze(request, field, analyzer, indexService != null ? indexService.getIndexAnalyzers() : null,
-                analysisRegistry, environment, maxTokenCount);
-        } catch (IOException e) {
-            throw new ElasticsearchException("analysis failed", e);
         }
 
+        // Otherwise we use a built-in analyzer, which should not be closed
+        return analyze(request, getAnalyzer(request, analysisRegistry, indexService), maxTokenCount);
+    }
+
+    private IndexService getIndexService(ShardId shardId) {
+        if (shardId != null) {
+            return indicesService.indexServiceSafe(shardId.getIndex());
+        }
+        return null;
     }
 
-    public static AnalyzeAction.Response analyze(AnalyzeAction.Request request, String field, Analyzer analyzer,
-                                                 IndexAnalyzers indexAnalyzers, AnalysisRegistry analysisRegistry,
-                                                 Environment environment, int maxTokenCount) throws IOException {
-        boolean closeAnalyzer = false;
-        if (analyzer == null && request.analyzer() != null) {
-            if (indexAnalyzers == null) {
-                analyzer = analysisRegistry.getAnalyzer(request.analyzer());
+    private static Analyzer getAnalyzer(AnalyzeAction.Request request, AnalysisRegistry analysisRegistry,
+                                        IndexService indexService) throws IOException {
+        if (request.analyzer() != null) {
+            if (indexService == null) {
+                Analyzer analyzer = analysisRegistry.getAnalyzer(request.analyzer());
                 if (analyzer == null) {
                     throw new IllegalArgumentException("failed to find global analyzer [" + request.analyzer() + "]");
                 }
+                return analyzer;
             } else {
-                analyzer = indexAnalyzers.get(request.analyzer());
+                Analyzer analyzer = indexService.getIndexAnalyzers().get(request.analyzer());
                 if (analyzer == null) {
                     throw new IllegalArgumentException("failed to find analyzer [" + request.analyzer() + "]");
                 }
+                return analyzer;
+            }
+        }
+        if (request.normalizer() != null) {
+            // Get normalizer from indexAnalyzers
+            if (indexService == null) {
+                throw new IllegalArgumentException("analysis based on a normalizer requires an index");
+            }
+            Analyzer analyzer = indexService.getIndexAnalyzers().getNormalizer(request.normalizer());
+            if (analyzer == null) {
+                throw new IllegalArgumentException("failed to find normalizer under [" + request.normalizer() + "]");
             }
-        } else if (request.tokenizer() != null) {
+        }
+        if (request.field() != null) {
+            if (indexService == null) {
+                throw new IllegalArgumentException("analysis based on a specific field requires an index");
+            }
+            MappedFieldType fieldType = indexService.mapperService().fullName(request.field());
+            if (fieldType != null) {
+                if (fieldType.tokenized() || fieldType instanceof KeywordFieldMapper.KeywordFieldType) {
+                    return fieldType.indexAnalyzer();
+                } else {
+                    throw new IllegalArgumentException("Can't process field [" + request.field() +
+                        "], Analysis requests are only supported on tokenized fields");
+                }
+            }
+        }
+        if (indexService == null) {
+            return analysisRegistry.getAnalyzer("standard");
+        } else {
+            return indexService.getIndexAnalyzers().getDefaultIndexAnalyzer();
+        }
+    }
+
+    private static Analyzer buildCustomAnalyzer(AnalyzeAction.Request request, AnalysisRegistry analysisRegistry,
+                                                IndexAnalyzers indexAnalyzers, Environment environment) throws IOException {
+        if (request.tokenizer() != null) {
             final IndexSettings indexSettings = indexAnalyzers == null ? null : indexAnalyzers.getIndexSettings();
             Tuple<String, TokenizerFactory> tokenizerFactory = parseTokenizerFactory(request, indexAnalyzers,
-                        analysisRegistry, environment);
+                analysisRegistry, environment);
 
             List<CharFilterFactory> charFilterFactoryList =
                 parseCharFilterFactories(request, indexSettings, analysisRegistry, environment, false);
@@ -197,18 +217,11 @@ public class TransportAnalyzeAction extends TransportSingleShardAction<AnalyzeAc
             List<TokenFilterFactory> tokenFilterFactoryList = parseTokenFilterFactories(request, indexSettings, analysisRegistry,
                 environment, tokenizerFactory, charFilterFactoryList, false);
 
-            analyzer = new CustomAnalyzer(tokenizerFactory.v1(), tokenizerFactory.v2(),
-                charFilterFactoryList.toArray(new CharFilterFactory[charFilterFactoryList.size()]),
-                tokenFilterFactoryList.toArray(new TokenFilterFactory[tokenFilterFactoryList.size()]));
-            closeAnalyzer = true;
-        } else if (request.normalizer() != null) {
-            // Get normalizer from indexAnalyzers
-            analyzer = indexAnalyzers.getNormalizer(request.normalizer());
-            if (analyzer == null) {
-                throw new IllegalArgumentException("failed to find normalizer under [" + request.normalizer() + "]");
-            }
+            return new CustomAnalyzer(tokenizerFactory.v1(), tokenizerFactory.v2(),
+                charFilterFactoryList.toArray(new CharFilterFactory[0]),
+                tokenFilterFactoryList.toArray(new TokenFilterFactory[0]));
         } else if (((request.tokenFilters() != null && request.tokenFilters().size() > 0)
-                || (request.charFilters() != null && request.charFilters().size() > 0))) {
+            || (request.charFilters() != null && request.charFilters().size() > 0))) {
             final IndexSettings indexSettings = indexAnalyzers == null ? null : indexAnalyzers.getIndexSettings();
             // custom normalizer = if normalizer == null but filter or char_filter is not null and tokenizer/analyzer is null
             // get charfilter and filter from request
@@ -222,46 +235,29 @@ public class TransportAnalyzeAction extends TransportSingleShardAction<AnalyzeAc
                 parseTokenFilterFactories(request, indexSettings, analysisRegistry, environment,
                     new Tuple<>(keywordTokenizerName, keywordTokenizerFactory), charFilterFactoryList, true);
 
-            analyzer = new CustomAnalyzer("keyword_for_normalizer",
-                keywordTokenizerFactory,
-                charFilterFactoryList.toArray(new CharFilterFactory[charFilterFactoryList.size()]),
-                tokenFilterFactoryList.toArray(new TokenFilterFactory[tokenFilterFactoryList.size()]));
-            closeAnalyzer = true;
-        } else if (analyzer == null) {
-            if (indexAnalyzers == null) {
-                analyzer = analysisRegistry.getAnalyzer("standard");
-            } else {
-                analyzer = indexAnalyzers.getDefaultIndexAnalyzer();
-            }
+            return new CustomAnalyzer("keyword_for_normalizer", keywordTokenizerFactory,
+                charFilterFactoryList.toArray(new CharFilterFactory[0]), tokenFilterFactoryList.toArray(new TokenFilterFactory[0]));
         }
-        if (analyzer == null) {
-            throw new IllegalArgumentException("failed to find analyzer");
-        }
-
-        List<AnalyzeAction.AnalyzeToken> tokens = null;
-        AnalyzeAction.DetailAnalyzeResponse detail = null;
+        return null;
+    }
 
+    private static AnalyzeAction.Response analyze(AnalyzeAction.Request request, Analyzer analyzer, int maxTokenCount) {
         if (request.explain()) {
-            detail = detailAnalyze(request, analyzer, field, maxTokenCount);
-        } else {
-            tokens = simpleAnalyze(request, analyzer, field, maxTokenCount);
+            return new AnalyzeAction.Response(null, detailAnalyze(request, analyzer, maxTokenCount));
         }
-
-        if (closeAnalyzer) {
-            analyzer.close();
-        }
-
-        return new AnalyzeAction.Response(tokens, detail);
+        return new AnalyzeAction.Response(simpleAnalyze(request, analyzer, maxTokenCount), null);
     }
 
     private static List<AnalyzeAction.AnalyzeToken> simpleAnalyze(AnalyzeAction.Request request,
-                                                                           Analyzer analyzer, String field, int maxTokenCount) {
+                                                                  Analyzer analyzer, int maxTokenCount) {
         TokenCounter tc = new TokenCounter(maxTokenCount);
         List<AnalyzeAction.AnalyzeToken> tokens = new ArrayList<>();
         int lastPosition = -1;
         int lastOffset = 0;
+        // Note that we always pass "" as the field to the various Analyzer methods, because
+        // the analyzers we use here are all field-specific and so ignore this parameter
         for (String text : request.text()) {
-            try (TokenStream stream = analyzer.tokenStream(field, text)) {
+            try (TokenStream stream = analyzer.tokenStream("", text)) {
                 stream.reset();
                 CharTermAttribute term = stream.addAttribute(CharTermAttribute.class);
                 PositionIncrementAttribute posIncr = stream.addAttribute(PositionIncrementAttribute.class);
@@ -282,8 +278,8 @@ public class TransportAnalyzeAction extends TransportSingleShardAction<AnalyzeAc
                 lastOffset += offset.endOffset();
                 lastPosition += posIncr.getPositionIncrement();
 
-                lastPosition += analyzer.getPositionIncrementGap(field);
-                lastOffset += analyzer.getOffsetGap(field);
+                lastPosition += analyzer.getPositionIncrementGap("");
+                lastOffset += analyzer.getOffsetGap("");
             } catch (IOException e) {
                 throw new ElasticsearchException("failed to analyze", e);
             }
@@ -292,7 +288,7 @@ public class TransportAnalyzeAction extends TransportSingleShardAction<AnalyzeAc
     }
 
     private static AnalyzeAction.DetailAnalyzeResponse detailAnalyze(AnalyzeAction.Request request, Analyzer analyzer,
-                                                                     String field, int maxTokenCount) {
+                                                                     int maxTokenCount) {
         AnalyzeAction.DetailAnalyzeResponse detailResponse;
         final Set<String> includeAttributes = new HashSet<>();
         if (request.attributes() != null) {
@@ -338,7 +334,7 @@ public class TransportAnalyzeAction extends TransportSingleShardAction<AnalyzeAc
                 // analyzing only tokenizer
                 Tokenizer tokenizer = tokenizerFactory.create();
                 tokenizer.setReader(reader);
-                tokenizerTokenListCreator.analyze(tokenizer, customAnalyzer, field, includeAttributes);
+                tokenizerTokenListCreator.analyze(tokenizer, customAnalyzer, includeAttributes);
 
                 // analyzing each tokenfilter
                 if (tokenFilterFactories != null) {
@@ -348,7 +344,7 @@ public class TransportAnalyzeAction extends TransportSingleShardAction<AnalyzeAc
                         }
                         TokenStream stream = createStackedTokenStream(request.text()[textIndex],
                             charFilterFactories, tokenizerFactory, tokenFilterFactories, tokenFilterIndex + 1);
-                        tokenFiltersTokenListCreator[tokenFilterIndex].analyze(stream, customAnalyzer, field, includeAttributes);
+                        tokenFiltersTokenListCreator[tokenFilterIndex].analyze(stream, customAnalyzer, includeAttributes);
                     }
                 }
             }
@@ -383,8 +379,8 @@ public class TransportAnalyzeAction extends TransportSingleShardAction<AnalyzeAc
 
             TokenListCreator tokenListCreator = new TokenListCreator(maxTokenCount);
             for (String text : request.text()) {
-                tokenListCreator.analyze(analyzer.tokenStream(field, text), analyzer, field,
-                        includeAttributes);
+                tokenListCreator.analyze(analyzer.tokenStream("", text), analyzer,
+                    includeAttributes);
             }
             detailResponse
                 = new AnalyzeAction.DetailAnalyzeResponse(new AnalyzeAction.AnalyzeTokenList(name, tokenListCreator.getArrayTokens()));
@@ -454,7 +450,7 @@ public class TransportAnalyzeAction extends TransportSingleShardAction<AnalyzeAc
             tc = new TokenCounter(maxTokenCount);
         }
 
-        private void analyze(TokenStream stream, Analyzer analyzer, String field, Set<String> includeAttributes) {
+        private void analyze(TokenStream stream, Analyzer analyzer, Set<String> includeAttributes) {
             try {
                 stream.reset();
                 CharTermAttribute term = stream.addAttribute(CharTermAttribute.class);
@@ -477,8 +473,8 @@ public class TransportAnalyzeAction extends TransportSingleShardAction<AnalyzeAc
                 lastOffset += offset.endOffset();
                 lastPosition += posIncr.getPositionIncrement();
 
-                lastPosition += analyzer.getPositionIncrementGap(field);
-                lastOffset += analyzer.getOffsetGap(field);
+                lastPosition += analyzer.getPositionIncrementGap("");
+                lastOffset += analyzer.getOffsetGap("");
 
             } catch (IOException e) {
                 throw new ElasticsearchException("failed to analyze", e);
@@ -488,7 +484,7 @@ public class TransportAnalyzeAction extends TransportSingleShardAction<AnalyzeAc
         }
 
         private AnalyzeAction.AnalyzeToken[] getArrayTokens() {
-            return tokens.toArray(new AnalyzeAction.AnalyzeToken[tokens.size()]);
+            return tokens.toArray(new AnalyzeAction.AnalyzeToken[0]);
         }
 
     }

+ 49 - 33
server/src/test/java/org/elasticsearch/action/admin/indices/TransportAnalyzeActionTests.java

@@ -29,6 +29,7 @@ import org.elasticsearch.common.UUIDs;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.env.Environment;
 import org.elasticsearch.env.TestEnvironment;
+import org.elasticsearch.index.IndexService;
 import org.elasticsearch.index.IndexSettings;
 import org.elasticsearch.index.analysis.AbstractCharFilterFactory;
 import org.elasticsearch.index.analysis.AbstractTokenFilterFactory;
@@ -52,6 +53,9 @@ import java.util.Map;
 
 import static java.util.Collections.singletonList;
 import static java.util.Collections.singletonMap;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 
 /**
  * Tests for {@link TransportAnalyzeAction}. See the rest tests in the {@code analysis-common} module for places where this code gets a ton
@@ -130,6 +134,12 @@ public class TransportAnalyzeActionTests extends ESTestCase {
         idxMaxTokenCount = idxSettings.getMaxTokenCount();
     }
 
+    private IndexService mockIndexService() {
+        IndexService is = mock(IndexService.class);
+        when(is.getIndexAnalyzers()).thenReturn(indexAnalyzers);
+        return is;
+    }
+
     /**
      * Test behavior when the named analysis component isn't defined on the index. In that case we should build with defaults.
      */
@@ -138,7 +148,8 @@ public class TransportAnalyzeActionTests extends ESTestCase {
         AnalyzeAction.Request request = new AnalyzeAction.Request();
         request.text("the quick brown fox");
         request.analyzer("standard");
-        AnalyzeAction.Response analyze = TransportAnalyzeAction.analyze(request, "text", null, null, registry, environment, maxTokenCount);
+        AnalyzeAction.Response analyze
+            = TransportAnalyzeAction.analyze(request, registry, environment, mockIndexService(), maxTokenCount);
         List<AnalyzeAction.AnalyzeToken> tokens = analyze.getTokens();
         assertEquals(4, tokens.size());
 
@@ -147,8 +158,8 @@ public class TransportAnalyzeActionTests extends ESTestCase {
         request.text("the qu1ck brown fox");
         request.tokenizer("standard");
         request.addTokenFilter("mock");
-        analyze = TransportAnalyzeAction.analyze(request, "text", null, randomBoolean() ? indexAnalyzers : null, registry, environment,
-            maxTokenCount);
+        analyze
+            = TransportAnalyzeAction.analyze(request, registry, environment, randomBoolean() ? mockIndexService() : null, maxTokenCount);
         tokens = analyze.getTokens();
         assertEquals(3, tokens.size());
         assertEquals("qu1ck", tokens.get(0).getTerm());
@@ -160,8 +171,8 @@ public class TransportAnalyzeActionTests extends ESTestCase {
         request.text("the qu1ck brown fox");
         request.tokenizer("standard");
         request.addCharFilter("append_foo");
-        analyze = TransportAnalyzeAction.analyze(request, "text", null, randomBoolean() ? indexAnalyzers : null, registry, environment,
-            maxTokenCount);
+        analyze
+            = TransportAnalyzeAction.analyze(request, registry, environment, randomBoolean() ? mockIndexService() : null, maxTokenCount);
         tokens = analyze.getTokens();
         assertEquals(4, tokens.size());
         assertEquals("the", tokens.get(0).getTerm());
@@ -175,8 +186,8 @@ public class TransportAnalyzeActionTests extends ESTestCase {
         request.tokenizer("standard");
         request.addCharFilter("append");
         request.text("the qu1ck brown fox");
-        analyze = TransportAnalyzeAction.analyze(request, "text", null, randomBoolean() ? indexAnalyzers : null, registry, environment,
-            maxTokenCount);
+        analyze
+            = TransportAnalyzeAction.analyze(request, registry, environment, randomBoolean() ? mockIndexService() : null, maxTokenCount);
         tokens = analyze.getTokens();
         assertEquals(4, tokens.size());
         assertEquals("the", tokens.get(0).getTerm());
@@ -189,7 +200,7 @@ public class TransportAnalyzeActionTests extends ESTestCase {
         AnalyzeAction.Request request = new AnalyzeAction.Request();
         request.analyzer("standard");
         request.text("the 1 brown fox");
-        AnalyzeAction.Response analyze = TransportAnalyzeAction.analyze(request, "text", null, null, registry, environment, maxTokenCount);
+        AnalyzeAction.Response analyze = TransportAnalyzeAction.analyze(request, registry, environment, null, maxTokenCount);
         List<AnalyzeAction.AnalyzeToken> tokens = analyze.getTokens();
         assertEquals(4, tokens.size());
         assertEquals("the", tokens.get(0).getTerm());
@@ -221,8 +232,8 @@ public class TransportAnalyzeActionTests extends ESTestCase {
         AnalyzeAction.Request request = new AnalyzeAction.Request();
         request.text("the quick brown fox");
         request.analyzer("custom_analyzer");
-        AnalyzeAction.Response analyze = TransportAnalyzeAction.analyze(request, "text", null, indexAnalyzers, registry, environment,
-            maxTokenCount);
+        AnalyzeAction.Response analyze
+            = TransportAnalyzeAction.analyze(request, registry, environment, mockIndexService(), maxTokenCount);
         List<AnalyzeAction.AnalyzeToken> tokens = analyze.getTokens();
         assertEquals(3, tokens.size());
         assertEquals("quick", tokens.get(0).getTerm());
@@ -230,7 +241,7 @@ public class TransportAnalyzeActionTests extends ESTestCase {
         assertEquals("fox", tokens.get(2).getTerm());
 
         request.analyzer("standard");
-        analyze = TransportAnalyzeAction.analyze(request, "text", null, indexAnalyzers, registry, environment, maxTokenCount);
+        analyze = TransportAnalyzeAction.analyze(request, registry, environment, mockIndexService(), maxTokenCount);
         tokens = analyze.getTokens();
         assertEquals(4, tokens.size());
         assertEquals("the", tokens.get(0).getTerm());
@@ -241,7 +252,7 @@ public class TransportAnalyzeActionTests extends ESTestCase {
         // Switch the analyzer out for just a tokenizer
         request.analyzer(null);
         request.tokenizer("standard");
-        analyze = TransportAnalyzeAction.analyze(request, "text", null, indexAnalyzers, registry, environment, maxTokenCount);
+        analyze = TransportAnalyzeAction.analyze(request, registry, environment, mockIndexService(), maxTokenCount);
         tokens = analyze.getTokens();
         assertEquals(4, tokens.size());
         assertEquals("the", tokens.get(0).getTerm());
@@ -251,7 +262,7 @@ public class TransportAnalyzeActionTests extends ESTestCase {
 
         // Now try applying our token filter
         request.addTokenFilter("mock");
-        analyze = TransportAnalyzeAction.analyze(request, "text", null, indexAnalyzers, registry, environment, maxTokenCount);
+        analyze = TransportAnalyzeAction.analyze(request, registry, environment, mockIndexService(), maxTokenCount);
         tokens = analyze.getTokens();
         assertEquals(3, tokens.size());
         assertEquals("quick", tokens.get(0).getTerm());
@@ -259,24 +270,32 @@ public class TransportAnalyzeActionTests extends ESTestCase {
         assertEquals("fox", tokens.get(2).getTerm());
     }
 
-    public void testGetIndexAnalyserWithoutIndexAnalyzers() throws IOException {
+    public void testGetIndexAnalyserWithoutIndexAnalyzers() {
         IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
             () -> TransportAnalyzeAction.analyze(
                 new AnalyzeAction.Request()
                     .analyzer("custom_analyzer")
                     .text("the qu1ck brown fox-dog"),
-                "text", null, null, registry, environment, maxTokenCount));
+                registry, environment, null, maxTokenCount));
         assertEquals(e.getMessage(), "failed to find global analyzer [custom_analyzer]");
     }
 
-    public void testUnknown() throws IOException {
+    public void testGetFieldAnalyzerWithoutIndexAnalyzers() {
+        AnalyzeAction.Request req = new AnalyzeAction.Request().field("field").text("text");
+        IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> {
+            TransportAnalyzeAction.analyze(req, registry, environment, null, maxTokenCount);
+        });
+        assertEquals(e.getMessage(), "analysis based on a specific field requires an index");
+    }
+
+    public void testUnknown() {
         boolean notGlobal = randomBoolean();
         IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
             () -> TransportAnalyzeAction.analyze(
                 new AnalyzeAction.Request()
                     .analyzer("foobar")
                     .text("the qu1ck brown fox"),
-                "text", null, notGlobal ? indexAnalyzers : null, registry, environment, maxTokenCount));
+                registry, environment, notGlobal ? mockIndexService() : null, maxTokenCount));
         if (notGlobal) {
             assertEquals(e.getMessage(), "failed to find analyzer [foobar]");
         } else {
@@ -288,7 +307,7 @@ public class TransportAnalyzeActionTests extends ESTestCase {
                 new AnalyzeAction.Request()
                     .tokenizer("foobar")
                     .text("the qu1ck brown fox"),
-                "text", null, notGlobal ? indexAnalyzers : null, registry, environment, maxTokenCount));
+                registry, environment, notGlobal ? mockIndexService() : null, maxTokenCount));
         if (notGlobal) {
             assertEquals(e.getMessage(), "failed to find tokenizer under [foobar]");
         } else {
@@ -301,7 +320,7 @@ public class TransportAnalyzeActionTests extends ESTestCase {
                     .tokenizer("standard")
                     .addTokenFilter("foobar")
                     .text("the qu1ck brown fox"),
-                "text", null, notGlobal ? indexAnalyzers : null, registry, environment, maxTokenCount));
+                registry, environment, notGlobal ? mockIndexService() : null, maxTokenCount));
         if (notGlobal) {
             assertEquals(e.getMessage(), "failed to find token filter under [foobar]");
         } else {
@@ -315,7 +334,7 @@ public class TransportAnalyzeActionTests extends ESTestCase {
                     .addTokenFilter("lowercase")
                     .addCharFilter("foobar")
                     .text("the qu1ck brown fox"),
-                "text", null, notGlobal ? indexAnalyzers : null, registry, environment, maxTokenCount));
+                registry, environment, notGlobal ? mockIndexService() : null, maxTokenCount));
         if (notGlobal) {
             assertEquals(e.getMessage(), "failed to find char filter under [foobar]");
         } else {
@@ -327,7 +346,7 @@ public class TransportAnalyzeActionTests extends ESTestCase {
                 new AnalyzeAction.Request()
                     .normalizer("foobar")
                     .text("the qu1ck brown fox"),
-                "text", null, indexAnalyzers, registry, environment, maxTokenCount));
+                registry, environment, mockIndexService(), maxTokenCount));
         assertEquals(e.getMessage(), "failed to find normalizer under [foobar]");
     }
 
@@ -336,8 +355,8 @@ public class TransportAnalyzeActionTests extends ESTestCase {
         request.tokenizer("standard");
         request.addTokenFilter("stop"); // stop token filter is not prebuilt in AnalysisModule#setupPreConfiguredTokenFilters()
         request.text("the quick brown fox");
-        AnalyzeAction.Response analyze = TransportAnalyzeAction.analyze(request, "text", null, indexAnalyzers, registry, environment,
-            maxTokenCount);
+        AnalyzeAction.Response analyze
+            = TransportAnalyzeAction.analyze(request, registry, environment, mockIndexService(), maxTokenCount);
         List<AnalyzeAction.AnalyzeToken> tokens = analyze.getTokens();
         assertEquals(3, tokens.size());
         assertEquals("quick", tokens.get(0).getTerm());
@@ -349,8 +368,8 @@ public class TransportAnalyzeActionTests extends ESTestCase {
         AnalyzeAction.Request request = new AnalyzeAction.Request("index");
         request.normalizer("my_normalizer");
         request.text("ABc");
-        AnalyzeAction.Response analyze = TransportAnalyzeAction.analyze(request, "text", null, indexAnalyzers, registry, environment,
-            maxTokenCount);
+        AnalyzeAction.Response analyze
+            = TransportAnalyzeAction.analyze(request, registry, environment, mockIndexService(), maxTokenCount);
         List<AnalyzeAction.AnalyzeToken> tokens = analyze.getTokens();
 
         assertEquals(1, tokens.size());
@@ -361,7 +380,7 @@ public class TransportAnalyzeActionTests extends ESTestCase {
      * This test is equivalent of calling _analyze without a specific index.
      * The default value for the maximum token count is used.
      */
-    public void testExceedDefaultMaxTokenLimit() throws IOException{
+    public void testExceedDefaultMaxTokenLimit() {
         // create a string with No. words more than maxTokenCount
         StringBuilder sbText = new StringBuilder();
         for (int i = 0; i <= maxTokenCount; i++){
@@ -375,8 +394,7 @@ public class TransportAnalyzeActionTests extends ESTestCase {
         request.text(text);
         request.analyzer("standard");
         IllegalStateException e = expectThrows(IllegalStateException.class,
-            () -> TransportAnalyzeAction.analyze(
-                request, "text", null, null, registry, environment, maxTokenCount));
+            () -> TransportAnalyzeAction.analyze(request, registry, environment, null, maxTokenCount));
         assertEquals(e.getMessage(), "The number of tokens produced by calling _analyze has exceeded the allowed maximum of ["
             + maxTokenCount + "]." + " This limit can be set by changing the [index.analyze.max_token_count] index level setting.");
 
@@ -386,8 +404,7 @@ public class TransportAnalyzeActionTests extends ESTestCase {
         request2.analyzer("standard");
         request2.explain(true);
         IllegalStateException e2 = expectThrows(IllegalStateException.class,
-            () -> TransportAnalyzeAction.analyze(
-                request2, "text", null, null, registry, environment, maxTokenCount));
+            () -> TransportAnalyzeAction.analyze(request2, registry, environment, null, maxTokenCount));
         assertEquals(e2.getMessage(), "The number of tokens produced by calling _analyze has exceeded the allowed maximum of ["
             + maxTokenCount + "]." + " This limit can be set by changing the [index.analyze.max_token_count] index level setting.");
     }
@@ -396,7 +413,7 @@ public class TransportAnalyzeActionTests extends ESTestCase {
      * This test is equivalent of calling _analyze against a specific index.
      * The index specific value for the maximum token count is used.
      */
-    public void testExceedSetMaxTokenLimit() throws IOException{
+    public void testExceedSetMaxTokenLimit() {
         // create a string with No. words more than idxMaxTokenCount
         StringBuilder sbText = new StringBuilder();
         for (int i = 0; i <= idxMaxTokenCount; i++){
@@ -409,8 +426,7 @@ public class TransportAnalyzeActionTests extends ESTestCase {
         request.text(text);
         request.analyzer("standard");
         IllegalStateException e = expectThrows(IllegalStateException.class,
-            () -> TransportAnalyzeAction.analyze(
-                request, "text", null, indexAnalyzers, registry, environment, idxMaxTokenCount));
+            () -> TransportAnalyzeAction.analyze(request, registry, environment, null, idxMaxTokenCount));
         assertEquals(e.getMessage(), "The number of tokens produced by calling _analyze has exceeded the allowed maximum of ["
             + idxMaxTokenCount + "]." + " This limit can be set by changing the [index.analyze.max_token_count] index level setting.");
     }

+ 31 - 1
server/src/test/java/org/elasticsearch/action/admin/indices/analyze/AnalyzeRequestTests.java

@@ -26,10 +26,11 @@ import org.elasticsearch.test.ESTestCase;
 
 import java.io.IOException;
 
+import static org.hamcrest.CoreMatchers.containsString;
 
 public class AnalyzeRequestTests extends ESTestCase {
 
-    public void testValidation() throws Exception {
+    public void testValidation() {
         AnalyzeAction.Request request = new AnalyzeAction.Request();
 
         ActionRequestValidationException e = request.validate();
@@ -66,6 +67,35 @@ public class AnalyzeRequestTests extends ESTestCase {
         requestAnalyzer.analyzer("analyzer");
         e = requestAnalyzer.validate();
         assertTrue(e.getMessage().contains("tokenizer/analyze should be null if normalizer is specified"));
+
+        {
+            AnalyzeAction.Request analyzerPlusDefs = new AnalyzeAction.Request("index");
+            analyzerPlusDefs.text("text");
+            analyzerPlusDefs.analyzer("analyzer");
+            analyzerPlusDefs.addTokenFilter("tokenfilter");
+            e = analyzerPlusDefs.validate();
+            assertNotNull(e);
+            assertThat(e.getMessage(), containsString("cannot define extra components on a named analyzer"));
+        }
+
+        {
+            AnalyzeAction.Request analyzerPlusDefs = new AnalyzeAction.Request("index");
+            analyzerPlusDefs.text("text");
+            analyzerPlusDefs.normalizer("normalizer");
+            analyzerPlusDefs.addTokenFilter("tokenfilter");
+            e = analyzerPlusDefs.validate();
+            assertNotNull(e);
+            assertThat(e.getMessage(), containsString("cannot define extra components on a named normalizer"));
+        }
+        {
+            AnalyzeAction.Request analyzerPlusDefs = new AnalyzeAction.Request("index");
+            analyzerPlusDefs.text("text");
+            analyzerPlusDefs.field("field");
+            analyzerPlusDefs.addTokenFilter("tokenfilter");
+            e = analyzerPlusDefs.validate();
+            assertNotNull(e);
+            assertThat(e.getMessage(), containsString("cannot define extra components on a field-specific analyzer"));
+        }
     }
 
     public void testSerialization() throws IOException {