Browse Source

[ML] Consistent pattern for strict/lenient parser names (#32399)

Previously we had two patterns for naming of strict
and lenient parsers.

Some classes had CONFIG_PARSER and METADATA_PARSER,
and used an enum to pass the parser type to nested
parsers.

Other classes had STRICT_PARSER and LENIENT_PARSER
and used ternary operators to pass the parser type
to nested parsers.

This change makes all ML classes use the second of
the patterns described above.
David Roberts 7 years ago
parent
commit
0afa265ac9
36 changed files with 321 additions and 418 deletions
  1. 1 1
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java
  2. 4 4
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/MlMetadata.java
  3. 0 19
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/MlParserType.java
  4. 1 1
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/PutDatafeedAction.java
  5. 1 1
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/PutJobAction.java
  6. 1 1
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/ValidateDetectorAction.java
  7. 1 1
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/ValidateJobConfigAction.java
  8. 21 29
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/datafeed/ChunkingConfig.java
  9. 40 40
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/datafeed/DatafeedConfig.java
  10. 1 1
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/datafeed/DatafeedUpdate.java
  11. 31 42
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/AnalysisConfig.java
  12. 20 28
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/AnalysisLimits.java
  13. 5 5
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/CategorizationAnalyzerConfig.java
  14. 14 21
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/DataDescription.java
  15. 12 20
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/DetectionRule.java
  16. 25 30
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/Detector.java
  17. 16 25
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/FilterRef.java
  18. 58 59
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/Job.java
  19. 3 3
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/JobUpdate.java
  20. 11 21
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/ModelPlotConfig.java
  21. 22 32
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/RuleCondition.java
  22. 3 3
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/RuleScope.java
  23. 1 1
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/datafeed/ChunkingConfigTests.java
  24. 3 3
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/datafeed/DatafeedConfigTests.java
  25. 1 1
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/job/config/AnalysisConfigTests.java
  26. 9 9
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/job/config/AnalysisLimitsTests.java
  27. 4 4
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/job/config/DataDescriptionTests.java
  28. 1 1
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/job/config/DetectionRuleTests.java
  29. 1 1
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/job/config/DetectorTests.java
  30. 2 2
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/job/config/FilterRefTests.java
  31. 3 3
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/job/config/JobTests.java
  32. 1 1
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/job/config/ModelPlotConfigTests.java
  33. 1 1
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/job/config/RuleConditionTests.java
  34. 1 1
      x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/MlMetadataTests.java
  35. 1 2
      x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/job/config/CategorizationAnalyzerConfigTests.java
  36. 1 1
      x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/job/config/JobBuilderTests.java

+ 1 - 1
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java

@@ -373,7 +373,7 @@ public class XPackClientPlugin extends Plugin implements ActionPlugin, NetworkPl
         return Arrays.asList(
                 // ML - Custom metadata
                 new NamedXContentRegistry.Entry(MetaData.Custom.class, new ParseField("ml"),
-                        parser -> MlMetadata.METADATA_PARSER.parse(parser, null).build()),
+                        parser -> MlMetadata.LENIENT_PARSER.parse(parser, null).build()),
                 // ML - Persistent action requests
                 new NamedXContentRegistry.Entry(PersistentTaskParams.class, new ParseField(StartDatafeedAction.TASK_NAME),
                         StartDatafeedAction.DatafeedParams::fromXContent),

+ 4 - 4
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/MlMetadata.java

@@ -61,12 +61,12 @@ public class MlMetadata implements XPackPlugin.XPackMetaDataCustom {
 
     public static final MlMetadata EMPTY_METADATA = new MlMetadata(Collections.emptySortedMap(), Collections.emptySortedMap());
     // This parser follows the pattern that metadata is parsed leniently (to allow for enhancements)
-    public static final ObjectParser<Builder, Void> METADATA_PARSER = new ObjectParser<>("ml_metadata", true, Builder::new);
+    public static final ObjectParser<Builder, Void> LENIENT_PARSER = new ObjectParser<>("ml_metadata", true, Builder::new);
 
     static {
-        METADATA_PARSER.declareObjectArray(Builder::putJobs, (p, c) -> Job.METADATA_PARSER.apply(p, c).build(), JOBS_FIELD);
-        METADATA_PARSER.declareObjectArray(Builder::putDatafeeds,
-                (p, c) -> DatafeedConfig.METADATA_PARSER.apply(p, c).build(), DATAFEEDS_FIELD);
+        LENIENT_PARSER.declareObjectArray(Builder::putJobs, (p, c) -> Job.LENIENT_PARSER.apply(p, c).build(), JOBS_FIELD);
+        LENIENT_PARSER.declareObjectArray(Builder::putDatafeeds,
+                (p, c) -> DatafeedConfig.LENIENT_PARSER.apply(p, c).build(), DATAFEEDS_FIELD);
     }
 
     private final SortedMap<String, Job> jobs;

+ 0 - 19
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/MlParserType.java

@@ -1,19 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-package org.elasticsearch.xpack.core.ml;
-
-/**
- * In order to allow enhancements that require additions to the ML custom cluster state to be made in minor versions,
- * when we parse our metadata from persisted cluster state we ignore unknown fields.  However, we don't want to be
- * lenient when parsing config as this would mean user mistakes could go undetected.  Therefore, for all JSON objects
- * that are used in both custom cluster state and config we have two parsers, one tolerant of unknown fields (for
- * parsing cluster state) and one strict (for parsing config).  This class enumerates the two options.
- */
-public enum MlParserType {
-
-    METADATA, CONFIG;
-
-}

+ 1 - 1
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/PutDatafeedAction.java

@@ -39,7 +39,7 @@ public class PutDatafeedAction extends Action<PutDatafeedAction.Response> {
     public static class Request extends AcknowledgedRequest<Request> implements ToXContentObject {
 
         public static Request parseRequest(String datafeedId, XContentParser parser) {
-            DatafeedConfig.Builder datafeed = DatafeedConfig.CONFIG_PARSER.apply(parser, null);
+            DatafeedConfig.Builder datafeed = DatafeedConfig.STRICT_PARSER.apply(parser, null);
             datafeed.setId(datafeedId);
             return new Request(datafeed.build());
         }

+ 1 - 1
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/PutJobAction.java

@@ -42,7 +42,7 @@ public class PutJobAction extends Action<PutJobAction.Response> {
     public static class Request extends AcknowledgedRequest<Request> implements ToXContentObject {
 
         public static Request parseRequest(String jobId, XContentParser parser) {
-            Job.Builder jobBuilder = Job.CONFIG_PARSER.apply(parser, null);
+            Job.Builder jobBuilder = Job.STRICT_PARSER.apply(parser, null);
             if (jobBuilder.getId() == null) {
                 jobBuilder.setId(jobId);
             } else if (!Strings.isNullOrEmpty(jobId) && !jobId.equals(jobBuilder.getId())) {

+ 1 - 1
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/ValidateDetectorAction.java

@@ -48,7 +48,7 @@ public class ValidateDetectorAction extends Action<ValidateDetectorAction.Respon
         private Detector detector;
 
         public static Request parseRequest(XContentParser parser) {
-            Detector detector = Detector.CONFIG_PARSER.apply(parser, null).build();
+            Detector detector = Detector.STRICT_PARSER.apply(parser, null).build();
             return new Request(detector);
         }
 

+ 1 - 1
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/ValidateJobConfigAction.java

@@ -49,7 +49,7 @@ public class ValidateJobConfigAction extends Action<ValidateJobConfigAction.Resp
         private Job job;
 
         public static Request parseRequest(XContentParser parser) {
-            Job.Builder job = Job.CONFIG_PARSER.apply(parser, null);
+            Job.Builder job = Job.STRICT_PARSER.apply(parser, null);
             // When jobs are PUT their ID must be supplied in the URL - assume this will
             // be valid unless an invalid job ID is specified in the JSON to be validated
             job.setId(job.getId() != null ? job.getId() : "ok");

+ 21 - 29
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/datafeed/ChunkingConfig.java

@@ -16,13 +16,10 @@ import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
 import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
-import org.elasticsearch.xpack.core.ml.MlParserType;
 import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
 
 import java.io.IOException;
-import java.util.EnumMap;
 import java.util.Locale;
-import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -34,32 +31,27 @@ public class ChunkingConfig implements ToXContentObject, Writeable {
     public static final ParseField TIME_SPAN_FIELD = new ParseField("time_span");
 
     // These parsers follow the pattern that metadata is parsed leniently (to allow for enhancements), whilst config is parsed strictly
-    public static final ConstructingObjectParser<ChunkingConfig, Void> METADATA_PARSER = new ConstructingObjectParser<>(
-            "chunking_config", true, a -> new ChunkingConfig((Mode) a[0], (TimeValue) a[1]));
-    public static final ConstructingObjectParser<ChunkingConfig, Void> CONFIG_PARSER = new ConstructingObjectParser<>(
-            "chunking_config", false, a -> new ChunkingConfig((Mode) a[0], (TimeValue) a[1]));
-    public static final Map<MlParserType, ConstructingObjectParser<ChunkingConfig, Void>> PARSERS =
-            new EnumMap<>(MlParserType.class);
-
-    static {
-        PARSERS.put(MlParserType.METADATA, METADATA_PARSER);
-        PARSERS.put(MlParserType.CONFIG, CONFIG_PARSER);
-        for (MlParserType parserType : MlParserType.values()) {
-            ConstructingObjectParser<ChunkingConfig, Void> parser = PARSERS.get(parserType);
-            assert parser != null;
-            parser.declareField(ConstructingObjectParser.constructorArg(), p -> {
-                if (p.currentToken() == XContentParser.Token.VALUE_STRING) {
-                    return Mode.fromString(p.text());
-                }
-                throw new IllegalArgumentException("Unsupported token [" + p.currentToken() + "]");
-            }, MODE_FIELD, ValueType.STRING);
-            parser.declareField(ConstructingObjectParser.optionalConstructorArg(), p -> {
-                if (p.currentToken() == XContentParser.Token.VALUE_STRING) {
-                    return TimeValue.parseTimeValue(p.text(), TIME_SPAN_FIELD.getPreferredName());
-                }
-                throw new IllegalArgumentException("Unsupported token [" + p.currentToken() + "]");
-            }, TIME_SPAN_FIELD, ValueType.STRING);
-        }
+    public static final ConstructingObjectParser<ChunkingConfig, Void> LENIENT_PARSER = createParser(true);
+    public static final ConstructingObjectParser<ChunkingConfig, Void> STRICT_PARSER = createParser(false);
+
+    private static ConstructingObjectParser<ChunkingConfig, Void> createParser(boolean ignoreUnknownFields) {
+        ConstructingObjectParser<ChunkingConfig, Void> parser = new ConstructingObjectParser<>(
+            "chunking_config", ignoreUnknownFields, a -> new ChunkingConfig((Mode) a[0], (TimeValue) a[1]));
+
+        parser.declareField(ConstructingObjectParser.constructorArg(), p -> {
+            if (p.currentToken() == XContentParser.Token.VALUE_STRING) {
+                return Mode.fromString(p.text());
+            }
+            throw new IllegalArgumentException("Unsupported token [" + p.currentToken() + "]");
+        }, MODE_FIELD, ValueType.STRING);
+        parser.declareField(ConstructingObjectParser.optionalConstructorArg(), p -> {
+            if (p.currentToken() == XContentParser.Token.VALUE_STRING) {
+                return TimeValue.parseTimeValue(p.text(), TIME_SPAN_FIELD.getPreferredName());
+            }
+            throw new IllegalArgumentException("Unsupported token [" + p.currentToken() + "]");
+        }, TIME_SPAN_FIELD, ValueType.STRING);
+
+        return parser;
     }
 
     private final Mode mode;

+ 40 - 40
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/datafeed/DatafeedConfig.java

@@ -25,7 +25,6 @@ import org.elasticsearch.search.aggregations.AggregatorFactories;
 import org.elasticsearch.search.aggregations.metrics.max.MaxAggregationBuilder;
 import org.elasticsearch.search.aggregations.support.ValuesSourceAggregationBuilder;
 import org.elasticsearch.search.builder.SearchSourceBuilder;
-import org.elasticsearch.xpack.core.ml.MlParserType;
 import org.elasticsearch.xpack.core.ml.datafeed.extractor.ExtractorUtils;
 import org.elasticsearch.xpack.core.ml.job.config.Job;
 import org.elasticsearch.xpack.core.ml.job.messages.Messages;
@@ -38,7 +37,6 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.EnumMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -87,44 +85,46 @@ public class DatafeedConfig extends AbstractDiffable<DatafeedConfig> implements
     public static final ParseField HEADERS = new ParseField("headers");
 
     // These parsers follow the pattern that metadata is parsed leniently (to allow for enhancements), whilst config is parsed strictly
-    public static final ObjectParser<Builder, Void> METADATA_PARSER = new ObjectParser<>("datafeed_config", true, Builder::new);
-    public static final ObjectParser<Builder, Void> CONFIG_PARSER = new ObjectParser<>("datafeed_config", false, Builder::new);
-    public static final Map<MlParserType, ObjectParser<Builder, Void>> PARSERS = new EnumMap<>(MlParserType.class);
-
-    static {
-        PARSERS.put(MlParserType.METADATA, METADATA_PARSER);
-        PARSERS.put(MlParserType.CONFIG, CONFIG_PARSER);
-        for (MlParserType parserType : MlParserType.values()) {
-            ObjectParser<Builder, Void> parser = PARSERS.get(parserType);
-            assert parser != null;
-            parser.declareString(Builder::setId, ID);
-            parser.declareString(Builder::setJobId, Job.ID);
-            parser.declareStringArray(Builder::setIndices, INDEXES);
-            parser.declareStringArray(Builder::setIndices, INDICES);
-            parser.declareStringArray(Builder::setTypes, TYPES);
-            parser.declareString((builder, val) ->
-                    builder.setQueryDelay(TimeValue.parseTimeValue(val, QUERY_DELAY.getPreferredName())), QUERY_DELAY);
-            parser.declareString((builder, val) ->
-                    builder.setFrequency(TimeValue.parseTimeValue(val, FREQUENCY.getPreferredName())), FREQUENCY);
-            parser.declareObject(Builder::setQuery, (p, c) -> AbstractQueryBuilder.parseInnerQueryBuilder(p), QUERY);
-            parser.declareObject(Builder::setAggregations, (p, c) -> AggregatorFactories.parseAggregators(p), AGGREGATIONS);
-            parser.declareObject(Builder::setAggregations, (p, c) -> AggregatorFactories.parseAggregators(p), AGGS);
-            parser.declareObject(Builder::setScriptFields, (p, c) -> {
-                List<SearchSourceBuilder.ScriptField> parsedScriptFields = new ArrayList<>();
-                while (p.nextToken() != XContentParser.Token.END_OBJECT) {
-                    parsedScriptFields.add(new SearchSourceBuilder.ScriptField(p));
-                }
-                parsedScriptFields.sort(Comparator.comparing(SearchSourceBuilder.ScriptField::fieldName));
-                return parsedScriptFields;
-            }, SCRIPT_FIELDS);
-            parser.declareInt(Builder::setScrollSize, SCROLL_SIZE);
-            // TODO this is to read former _source field. Remove in v7.0.0
-            parser.declareBoolean((builder, value) -> {}, SOURCE);
-            parser.declareObject(Builder::setChunkingConfig, ChunkingConfig.PARSERS.get(parserType), CHUNKING_CONFIG);
-        }
-        // Headers are only parsed by the metadata parser, so headers supplied in the _body_ of a REST request will be rejected.
-        // (For config headers are explicitly transferred from the auth headers by code in the put/update datafeed actions.)
-        METADATA_PARSER.declareObject(Builder::setHeaders, (p, c) -> p.mapStrings(), HEADERS);
+    public static final ObjectParser<Builder, Void> LENIENT_PARSER = createParser(true);
+    public static final ObjectParser<Builder, Void> STRICT_PARSER = createParser(false);
+
+    private static ObjectParser<Builder, Void> createParser(boolean ignoreUnknownFields) {
+        ObjectParser<Builder, Void> parser = new ObjectParser<>("datafeed_config", ignoreUnknownFields, Builder::new);
+
+        parser.declareString(Builder::setId, ID);
+        parser.declareString(Builder::setJobId, Job.ID);
+        parser.declareStringArray(Builder::setIndices, INDEXES);
+        parser.declareStringArray(Builder::setIndices, INDICES);
+        parser.declareStringArray(Builder::setTypes, TYPES);
+        parser.declareString((builder, val) ->
+            builder.setQueryDelay(TimeValue.parseTimeValue(val, QUERY_DELAY.getPreferredName())), QUERY_DELAY);
+        parser.declareString((builder, val) ->
+            builder.setFrequency(TimeValue.parseTimeValue(val, FREQUENCY.getPreferredName())), FREQUENCY);
+        parser.declareObject(Builder::setQuery, (p, c) -> AbstractQueryBuilder.parseInnerQueryBuilder(p), QUERY);
+        parser.declareObject(Builder::setAggregations, (p, c) -> AggregatorFactories.parseAggregators(p), AGGREGATIONS);
+        parser.declareObject(Builder::setAggregations, (p, c) -> AggregatorFactories.parseAggregators(p), AGGS);
+        parser.declareObject(Builder::setScriptFields, (p, c) -> {
+            List<SearchSourceBuilder.ScriptField> parsedScriptFields = new ArrayList<>();
+            while (p.nextToken() != XContentParser.Token.END_OBJECT) {
+                parsedScriptFields.add(new SearchSourceBuilder.ScriptField(p));
+            }
+            parsedScriptFields.sort(Comparator.comparing(SearchSourceBuilder.ScriptField::fieldName));
+            return parsedScriptFields;
+        }, SCRIPT_FIELDS);
+        parser.declareInt(Builder::setScrollSize, SCROLL_SIZE);
+        // TODO this is to read former _source field. Remove in v7.0.0
+        parser.declareBoolean((builder, value) -> {
+        }, SOURCE);
+        parser.declareObject(Builder::setChunkingConfig, ignoreUnknownFields ? ChunkingConfig.LENIENT_PARSER : ChunkingConfig.STRICT_PARSER,
+            CHUNKING_CONFIG);
+
+        if (ignoreUnknownFields) {
+            // Headers are not parsed by the strict (config) parser, so headers supplied in the _body_ of a REST request will be rejected.
+            // (For config, headers are explicitly transferred from the auth headers by code in the put/update datafeed actions.)
+            parser.declareObject(Builder::setHeaders, (p, c) -> p.mapStrings(), HEADERS);
+        }
+
+        return parser;
     }
 
     private final String id;

+ 1 - 1
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/datafeed/DatafeedUpdate.java

@@ -68,7 +68,7 @@ public class DatafeedUpdate implements Writeable, ToXContentObject {
             return parsedScriptFields;
         }, DatafeedConfig.SCRIPT_FIELDS);
         PARSER.declareInt(Builder::setScrollSize, DatafeedConfig.SCROLL_SIZE);
-        PARSER.declareObject(Builder::setChunkingConfig, ChunkingConfig.CONFIG_PARSER, DatafeedConfig.CHUNKING_CONFIG);
+        PARSER.declareObject(Builder::setChunkingConfig, ChunkingConfig.STRICT_PARSER, DatafeedConfig.CHUNKING_CONFIG);
     }
 
     private final String id;

+ 31 - 42
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/AnalysisConfig.java

@@ -16,7 +16,6 @@ import org.elasticsearch.common.xcontent.ConstructingObjectParser;
 import org.elasticsearch.common.xcontent.ObjectParser;
 import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.xpack.core.ml.MlParserType;
 import org.elasticsearch.xpack.core.ml.job.messages.Messages;
 import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
 import org.elasticsearch.xpack.core.ml.utils.time.TimeUtils;
@@ -24,10 +23,8 @@ import org.elasticsearch.xpack.core.ml.utils.time.TimeUtils;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.EnumMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.SortedSet;
@@ -76,46 +73,38 @@ public class AnalysisConfig implements ToXContentObject, Writeable {
     public static final long DEFAULT_RESULT_FINALIZATION_WINDOW = 2L;
 
     // These parsers follow the pattern that metadata is parsed leniently (to allow for enhancements), whilst config is parsed strictly
+    public static final ConstructingObjectParser<AnalysisConfig.Builder, Void> LENIENT_PARSER = createParser(true);
+    public static final ConstructingObjectParser<AnalysisConfig.Builder, Void> STRICT_PARSER = createParser(false);
+
     @SuppressWarnings("unchecked")
-    public static final ConstructingObjectParser<AnalysisConfig.Builder, Void> METADATA_PARSER =
-            new ConstructingObjectParser<>(ANALYSIS_CONFIG.getPreferredName(), true,
-                    a -> new AnalysisConfig.Builder((List<Detector>) a[0]));
-    @SuppressWarnings("unchecked")
-    public static final ConstructingObjectParser<AnalysisConfig.Builder, Void> CONFIG_PARSER =
-            new ConstructingObjectParser<>(ANALYSIS_CONFIG.getPreferredName(), false,
-                    a -> new AnalysisConfig.Builder((List<Detector>) a[0]));
-    public static final Map<MlParserType, ConstructingObjectParser<Builder, Void>> PARSERS =
-            new EnumMap<>(MlParserType.class);
-
-    static {
-        PARSERS.put(MlParserType.METADATA, METADATA_PARSER);
-        PARSERS.put(MlParserType.CONFIG, CONFIG_PARSER);
-        for (MlParserType parserType : MlParserType.values()) {
-            ConstructingObjectParser<AnalysisConfig.Builder, Void> parser = PARSERS.get(parserType);
-            assert parser != null;
-            parser.declareObjectArray(ConstructingObjectParser.constructorArg(),
-                    (p, c) -> Detector.PARSERS.get(parserType).apply(p, c).build(), DETECTORS);
-            parser.declareString((builder, val) ->
-                    builder.setBucketSpan(TimeValue.parseTimeValue(val, BUCKET_SPAN.getPreferredName())), BUCKET_SPAN);
-            parser.declareString(Builder::setCategorizationFieldName, CATEGORIZATION_FIELD_NAME);
-            parser.declareStringArray(Builder::setCategorizationFilters, CATEGORIZATION_FILTERS);
-            // This one is nasty - the syntax for analyzers takes either names or objects at many levels, hence it's not
-            // possible to simply declare whether the field is a string or object and a completely custom parser is required
-            parser.declareField(Builder::setCategorizationAnalyzerConfig,
-                    (p, c) -> CategorizationAnalyzerConfig.buildFromXContentFragment(p, parserType),
-                    CATEGORIZATION_ANALYZER, ObjectParser.ValueType.OBJECT_OR_STRING);
-            parser.declareString((builder, val) ->
-                    builder.setLatency(TimeValue.parseTimeValue(val, LATENCY.getPreferredName())), LATENCY);
-            parser.declareString(Builder::setSummaryCountFieldName, SUMMARY_COUNT_FIELD_NAME);
-            parser.declareStringArray(Builder::setInfluencers, INFLUENCERS);
-            parser.declareBoolean(Builder::setOverlappingBuckets, OVERLAPPING_BUCKETS);
-            parser.declareLong(Builder::setResultFinalizationWindow, RESULT_FINALIZATION_WINDOW);
-            parser.declareBoolean(Builder::setMultivariateByFields, MULTIVARIATE_BY_FIELDS);
-            parser.declareStringArray((builder, values) -> builder.setMultipleBucketSpans(
-                    values.stream().map(v -> TimeValue.parseTimeValue(v, MULTIPLE_BUCKET_SPANS.getPreferredName()))
-                            .collect(Collectors.toList())), MULTIPLE_BUCKET_SPANS);
-            parser.declareBoolean(Builder::setUsePerPartitionNormalization, USER_PER_PARTITION_NORMALIZATION);
-        }
+    private static ConstructingObjectParser<AnalysisConfig.Builder, Void> createParser(boolean ignoreUnknownFields) {
+        ConstructingObjectParser<AnalysisConfig.Builder, Void> parser = new ConstructingObjectParser<>(ANALYSIS_CONFIG.getPreferredName(),
+            ignoreUnknownFields, a -> new AnalysisConfig.Builder((List<Detector>) a[0]));
+
+        parser.declareObjectArray(ConstructingObjectParser.constructorArg(),
+            (p, c) -> (ignoreUnknownFields ? Detector.LENIENT_PARSER : Detector.STRICT_PARSER).apply(p, c).build(), DETECTORS);
+        parser.declareString((builder, val) ->
+            builder.setBucketSpan(TimeValue.parseTimeValue(val, BUCKET_SPAN.getPreferredName())), BUCKET_SPAN);
+        parser.declareString(Builder::setCategorizationFieldName, CATEGORIZATION_FIELD_NAME);
+        parser.declareStringArray(Builder::setCategorizationFilters, CATEGORIZATION_FILTERS);
+        // This one is nasty - the syntax for analyzers takes either names or objects at many levels, hence it's not
+        // possible to simply declare whether the field is a string or object and a completely custom parser is required
+        parser.declareField(Builder::setCategorizationAnalyzerConfig,
+            (p, c) -> CategorizationAnalyzerConfig.buildFromXContentFragment(p, ignoreUnknownFields),
+            CATEGORIZATION_ANALYZER, ObjectParser.ValueType.OBJECT_OR_STRING);
+        parser.declareString((builder, val) ->
+            builder.setLatency(TimeValue.parseTimeValue(val, LATENCY.getPreferredName())), LATENCY);
+        parser.declareString(Builder::setSummaryCountFieldName, SUMMARY_COUNT_FIELD_NAME);
+        parser.declareStringArray(Builder::setInfluencers, INFLUENCERS);
+        parser.declareBoolean(Builder::setOverlappingBuckets, OVERLAPPING_BUCKETS);
+        parser.declareLong(Builder::setResultFinalizationWindow, RESULT_FINALIZATION_WINDOW);
+        parser.declareBoolean(Builder::setMultivariateByFields, MULTIVARIATE_BY_FIELDS);
+        parser.declareStringArray((builder, values) -> builder.setMultipleBucketSpans(
+            values.stream().map(v -> TimeValue.parseTimeValue(v, MULTIPLE_BUCKET_SPANS.getPreferredName()))
+                .collect(Collectors.toList())), MULTIPLE_BUCKET_SPANS);
+        parser.declareBoolean(Builder::setUsePerPartitionNormalization, USER_PER_PARTITION_NORMALIZATION);
+
+        return parser;
     }
 
     /**

+ 20 - 28
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/AnalysisLimits.java

@@ -17,13 +17,10 @@ import org.elasticsearch.common.xcontent.ObjectParser;
 import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
-import org.elasticsearch.xpack.core.ml.MlParserType;
 import org.elasticsearch.xpack.core.ml.job.messages.Messages;
 import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
 
 import java.io.IOException;
-import java.util.EnumMap;
-import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -50,31 +47,26 @@ public class AnalysisLimits implements ToXContentObject, Writeable {
     public static final ParseField CATEGORIZATION_EXAMPLES_LIMIT = new ParseField("categorization_examples_limit");
 
     // These parsers follow the pattern that metadata is parsed leniently (to allow for enhancements), whilst config is parsed strictly
-    public static final ConstructingObjectParser<AnalysisLimits, Void> METADATA_PARSER = new ConstructingObjectParser<>(
-            "analysis_limits", true, a -> new AnalysisLimits(
-                    a[0] == null ? PRE_6_1_DEFAULT_MODEL_MEMORY_LIMIT_MB : (Long) a[0],
-                    a[1] == null ? DEFAULT_CATEGORIZATION_EXAMPLES_LIMIT : (Long) a[1]));
-    public static final ConstructingObjectParser<AnalysisLimits, Void> CONFIG_PARSER = new ConstructingObjectParser<>(
-            "analysis_limits", false, a -> new AnalysisLimits((Long) a[0], (Long) a[1]));
-    public static final Map<MlParserType, ConstructingObjectParser<AnalysisLimits, Void>> PARSERS =
-            new EnumMap<>(MlParserType.class);
-
-    static {
-        PARSERS.put(MlParserType.METADATA, METADATA_PARSER);
-        PARSERS.put(MlParserType.CONFIG, CONFIG_PARSER);
-        for (MlParserType parserType : MlParserType.values()) {
-            ConstructingObjectParser<AnalysisLimits, Void> parser = PARSERS.get(parserType);
-            assert parser != null;
-            parser.declareField(ConstructingObjectParser.optionalConstructorArg(), p -> {
-                if (p.currentToken() == XContentParser.Token.VALUE_STRING) {
-                    return ByteSizeValue.parseBytesSizeValue(p.text(), MODEL_MEMORY_LIMIT.getPreferredName()).getMb();
-                } else if (p.currentToken() == XContentParser.Token.VALUE_NUMBER) {
-                    return p.longValue();
-                }
-                throw new IllegalArgumentException("Unsupported token [" + p.currentToken() + "]");
-            }, MODEL_MEMORY_LIMIT, ObjectParser.ValueType.VALUE);
-            parser.declareLong(ConstructingObjectParser.optionalConstructorArg(), CATEGORIZATION_EXAMPLES_LIMIT);
-        }
+    public static final ConstructingObjectParser<AnalysisLimits, Void> LENIENT_PARSER = createParser(true);
+    public static final ConstructingObjectParser<AnalysisLimits, Void> STRICT_PARSER = createParser(false);
+
+    private static ConstructingObjectParser<AnalysisLimits, Void> createParser(boolean ignoreUnknownFields) {
+        ConstructingObjectParser<AnalysisLimits, Void> parser = new ConstructingObjectParser<>(
+            "analysis_limits", ignoreUnknownFields, a -> ignoreUnknownFields ? new AnalysisLimits(
+                a[0] == null ? PRE_6_1_DEFAULT_MODEL_MEMORY_LIMIT_MB : (Long) a[0],
+                a[1] == null ? DEFAULT_CATEGORIZATION_EXAMPLES_LIMIT : (Long) a[1]) : new AnalysisLimits((Long) a[0], (Long) a[1]));
+
+        parser.declareField(ConstructingObjectParser.optionalConstructorArg(), p -> {
+            if (p.currentToken() == XContentParser.Token.VALUE_STRING) {
+                return ByteSizeValue.parseBytesSizeValue(p.text(), MODEL_MEMORY_LIMIT.getPreferredName()).getMb();
+            } else if (p.currentToken() == XContentParser.Token.VALUE_NUMBER) {
+                return p.longValue();
+            }
+            throw new IllegalArgumentException("Unsupported token [" + p.currentToken() + "]");
+        }, MODEL_MEMORY_LIMIT, ObjectParser.ValueType.VALUE);
+        parser.declareLong(ConstructingObjectParser.optionalConstructorArg(), CATEGORIZATION_EXAMPLES_LIMIT);
+
+        return parser;
     }
 
     /**

+ 5 - 5
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/CategorizationAnalyzerConfig.java

@@ -17,7 +17,6 @@ import org.elasticsearch.common.xcontent.XContentFactory;
 import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.rest.action.admin.indices.RestAnalyzeAction;
-import org.elasticsearch.xpack.core.ml.MlParserType;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -61,7 +60,8 @@ public class CategorizationAnalyzerConfig implements ToXContentFragment, Writeab
     /**
      * This method is only used in the unit tests - in production code this config is always parsed as a fragment.
      */
-    public static CategorizationAnalyzerConfig buildFromXContentObject(XContentParser parser, MlParserType parserType) throws IOException {
+    public static CategorizationAnalyzerConfig buildFromXContentObject(XContentParser parser, boolean ignoreUnknownFields)
+        throws IOException {
 
         if (parser.nextToken() != XContentParser.Token.START_OBJECT) {
             throw new IllegalArgumentException("Expected start object but got [" + parser.currentToken() + "]");
@@ -71,7 +71,7 @@ public class CategorizationAnalyzerConfig implements ToXContentFragment, Writeab
             throw new IllegalArgumentException("Expected [" + CATEGORIZATION_ANALYZER + "] field but got [" + parser.currentToken() + "]");
         }
         parser.nextToken();
-        CategorizationAnalyzerConfig categorizationAnalyzerConfig = buildFromXContentFragment(parser, parserType);
+        CategorizationAnalyzerConfig categorizationAnalyzerConfig = buildFromXContentFragment(parser, ignoreUnknownFields);
         parser.nextToken();
         return categorizationAnalyzerConfig;
     }
@@ -83,7 +83,7 @@ public class CategorizationAnalyzerConfig implements ToXContentFragment, Writeab
      *
      * The parser is strict when parsing config and lenient when parsing cluster state.
      */
-    static CategorizationAnalyzerConfig buildFromXContentFragment(XContentParser parser, MlParserType parserType) throws IOException {
+    static CategorizationAnalyzerConfig buildFromXContentFragment(XContentParser parser, boolean ignoreUnknownFields) throws IOException {
 
         CategorizationAnalyzerConfig.Builder builder = new CategorizationAnalyzerConfig.Builder();
 
@@ -131,7 +131,7 @@ public class CategorizationAnalyzerConfig implements ToXContentFragment, Writeab
                         }
                     }
                 // Be lenient when parsing cluster state - assume unknown fields are from future versions
-                } else if (parserType == MlParserType.CONFIG) {
+                } else if (ignoreUnknownFields == false) {
                     throw new IllegalArgumentException("Parameter [" + currentFieldName + "] in [" + CATEGORIZATION_ANALYZER +
                             "] is unknown or of the wrong type [" + token + "]");
                 }

+ 14 - 21
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/DataDescription.java

@@ -14,16 +14,13 @@ import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
 import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
-import org.elasticsearch.xpack.core.ml.MlParserType;
 import org.elasticsearch.xpack.core.ml.job.messages.Messages;
 import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
 import org.elasticsearch.xpack.core.ml.utils.time.DateTimeFormatterTimestampConverter;
 
 import java.io.IOException;
 import java.time.ZoneOffset;
-import java.util.EnumMap;
 import java.util.Locale;
-import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -126,24 +123,20 @@ public class DataDescription implements ToXContentObject, Writeable {
     private final Character quoteCharacter;
 
     // These parsers follow the pattern that metadata is parsed leniently (to allow for enhancements), whilst config is parsed strictly
-    public static final ObjectParser<Builder, Void> METADATA_PARSER =
-            new ObjectParser<>(DATA_DESCRIPTION_FIELD.getPreferredName(), true, Builder::new);
-    public static final ObjectParser<Builder, Void> CONFIG_PARSER =
-            new ObjectParser<>(DATA_DESCRIPTION_FIELD.getPreferredName(), false, Builder::new);
-    public static final Map<MlParserType, ObjectParser<Builder, Void>> PARSERS = new EnumMap<>(MlParserType.class);
-
-    static {
-        PARSERS.put(MlParserType.METADATA, METADATA_PARSER);
-        PARSERS.put(MlParserType.CONFIG, CONFIG_PARSER);
-        for (MlParserType parserType : MlParserType.values()) {
-            ObjectParser<Builder, Void> parser = PARSERS.get(parserType);
-            assert parser != null;
-            parser.declareString(Builder::setFormat, FORMAT_FIELD);
-            parser.declareString(Builder::setTimeField, TIME_FIELD_NAME_FIELD);
-            parser.declareString(Builder::setTimeFormat, TIME_FORMAT_FIELD);
-            parser.declareField(Builder::setFieldDelimiter, DataDescription::extractChar, FIELD_DELIMITER_FIELD, ValueType.STRING);
-            parser.declareField(Builder::setQuoteCharacter, DataDescription::extractChar, QUOTE_CHARACTER_FIELD, ValueType.STRING);
-        }
+    public static final ObjectParser<Builder, Void> LENIENT_PARSER = createParser(true);
+    public static final ObjectParser<Builder, Void> STRICT_PARSER = createParser(false);
+
+    private static ObjectParser<Builder, Void> createParser(boolean ignoreUnknownFields) {
+        ObjectParser<Builder, Void> parser =
+            new ObjectParser<>(DATA_DESCRIPTION_FIELD.getPreferredName(), ignoreUnknownFields, Builder::new);
+
+        parser.declareString(Builder::setFormat, FORMAT_FIELD);
+        parser.declareString(Builder::setTimeField, TIME_FIELD_NAME_FIELD);
+        parser.declareString(Builder::setTimeFormat, TIME_FORMAT_FIELD);
+        parser.declareField(Builder::setFieldDelimiter, DataDescription::extractChar, FIELD_DELIMITER_FIELD, ValueType.STRING);
+        parser.declareField(Builder::setQuoteCharacter, DataDescription::extractChar, QUOTE_CHARACTER_FIELD, ValueType.STRING);
+
+        return parser;
     }
 
     public DataDescription(DataFormat dataFormat, String timeFieldName, String timeFormat, Character fieldDelimiter,

+ 12 - 20
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/DetectionRule.java

@@ -13,17 +13,14 @@ import org.elasticsearch.common.io.stream.Writeable;
 import org.elasticsearch.common.xcontent.ObjectParser;
 import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.xpack.core.ml.MlParserType;
 import org.elasticsearch.xpack.core.ml.job.messages.Messages;
 import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
 
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.EnumMap;
 import java.util.EnumSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
@@ -37,23 +34,18 @@ public class DetectionRule implements ToXContentObject, Writeable {
     public static final ParseField CONDITIONS_FIELD = new ParseField("conditions");
 
     // These parsers follow the pattern that metadata is parsed leniently (to allow for enhancements), whilst config is parsed strictly
-    public static final ObjectParser<Builder, Void> METADATA_PARSER =
-            new ObjectParser<>(DETECTION_RULE_FIELD.getPreferredName(), true, Builder::new);
-    public static final ObjectParser<Builder, Void> CONFIG_PARSER =
-            new ObjectParser<>(DETECTION_RULE_FIELD.getPreferredName(), false, Builder::new);
-    public static final Map<MlParserType, ObjectParser<Builder, Void>> PARSERS = new EnumMap<>(MlParserType.class);
-
-    static {
-        PARSERS.put(MlParserType.METADATA, METADATA_PARSER);
-        PARSERS.put(MlParserType.CONFIG, CONFIG_PARSER);
-        for (MlParserType parserType : MlParserType.values()) {
-            ObjectParser<Builder, Void> parser = PARSERS.get(parserType);
-            assert parser != null;
-            parser.declareStringArray(Builder::setActions, ACTIONS_FIELD);
-            parser.declareObject(Builder::setScope, RuleScope.parser(parserType), SCOPE_FIELD);
-            parser.declareObjectArray(Builder::setConditions, (p, c) ->
-                    RuleCondition.PARSERS.get(parserType).apply(p, c), CONDITIONS_FIELD);
-        }
+    public static final ObjectParser<Builder, Void> LENIENT_PARSER = createParser(true);
+    public static final ObjectParser<Builder, Void> STRICT_PARSER = createParser(false);
+
+    private static ObjectParser<Builder, Void> createParser(boolean ignoreUnknownFields) {
+        ObjectParser<Builder, Void> parser = new ObjectParser<>(DETECTION_RULE_FIELD.getPreferredName(), ignoreUnknownFields, Builder::new);
+
+        parser.declareStringArray(Builder::setActions, ACTIONS_FIELD);
+        parser.declareObject(Builder::setScope, RuleScope.parser(ignoreUnknownFields), SCOPE_FIELD);
+        parser.declareObjectArray(Builder::setConditions, ignoreUnknownFields ? RuleCondition.LENIENT_PARSER : RuleCondition.STRICT_PARSER,
+            CONDITIONS_FIELD);
+
+        return parser;
     }
 
     private final EnumSet<RuleAction> actions;

+ 25 - 30
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/Detector.java

@@ -16,7 +16,6 @@ import org.elasticsearch.common.xcontent.ObjectParser;
 import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
-import org.elasticsearch.xpack.core.ml.MlParserType;
 import org.elasticsearch.xpack.core.ml.job.messages.Messages;
 import org.elasticsearch.xpack.core.ml.job.process.autodetect.writer.RecordWriter;
 import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
@@ -26,12 +25,10 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.EnumMap;
 import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.TreeSet;
@@ -89,33 +86,31 @@ public class Detector implements ToXContentObject, Writeable {
     public static final ParseField DETECTOR_INDEX = new ParseField("detector_index");
 
     // These parsers follow the pattern that metadata is parsed leniently (to allow for enhancements), whilst config is parsed strictly
-    public static final ObjectParser<Builder, Void> METADATA_PARSER = new ObjectParser<>("detector", true, Builder::new);
-    public static final ObjectParser<Builder, Void> CONFIG_PARSER = new ObjectParser<>("detector", false, Builder::new);
-    public static final Map<MlParserType, ObjectParser<Builder, Void>> PARSERS = new EnumMap<>(MlParserType.class);
-
-    static {
-        PARSERS.put(MlParserType.METADATA, METADATA_PARSER);
-        PARSERS.put(MlParserType.CONFIG, CONFIG_PARSER);
-        for (MlParserType parserType : MlParserType.values()) {
-            ObjectParser<Builder, Void> parser = PARSERS.get(parserType);
-            assert parser != null;
-            parser.declareString(Builder::setDetectorDescription, DETECTOR_DESCRIPTION_FIELD);
-            parser.declareString(Builder::setFunction, FUNCTION_FIELD);
-            parser.declareString(Builder::setFieldName, FIELD_NAME_FIELD);
-            parser.declareString(Builder::setByFieldName, BY_FIELD_NAME_FIELD);
-            parser.declareString(Builder::setOverFieldName, OVER_FIELD_NAME_FIELD);
-            parser.declareString(Builder::setPartitionFieldName, PARTITION_FIELD_NAME_FIELD);
-            parser.declareBoolean(Builder::setUseNull, USE_NULL_FIELD);
-            parser.declareField(Builder::setExcludeFrequent, p -> {
-                if (p.currentToken() == XContentParser.Token.VALUE_STRING) {
-                    return ExcludeFrequent.forString(p.text());
-                }
-                throw new IllegalArgumentException("Unsupported token [" + p.currentToken() + "]");
-            }, EXCLUDE_FREQUENT_FIELD, ObjectParser.ValueType.STRING);
-            parser.declareObjectArray(Builder::setRules, (p, c) ->
-                    DetectionRule.PARSERS.get(parserType).apply(p, c).build(), CUSTOM_RULES_FIELD);
-            parser.declareInt(Builder::setDetectorIndex, DETECTOR_INDEX);
-        }
+    public static final ObjectParser<Builder, Void> LENIENT_PARSER = createParser(true);
+    public static final ObjectParser<Builder, Void> STRICT_PARSER = createParser(false);
+
+    private static ObjectParser<Builder, Void> createParser(boolean ignoreUnknownFields) {
+        ObjectParser<Builder, Void> parser = new ObjectParser<>("detector", ignoreUnknownFields, Builder::new);
+
+        parser.declareString(Builder::setDetectorDescription, DETECTOR_DESCRIPTION_FIELD);
+        parser.declareString(Builder::setFunction, FUNCTION_FIELD);
+        parser.declareString(Builder::setFieldName, FIELD_NAME_FIELD);
+        parser.declareString(Builder::setByFieldName, BY_FIELD_NAME_FIELD);
+        parser.declareString(Builder::setOverFieldName, OVER_FIELD_NAME_FIELD);
+        parser.declareString(Builder::setPartitionFieldName, PARTITION_FIELD_NAME_FIELD);
+        parser.declareBoolean(Builder::setUseNull, USE_NULL_FIELD);
+        parser.declareField(Builder::setExcludeFrequent, p -> {
+            if (p.currentToken() == XContentParser.Token.VALUE_STRING) {
+                return ExcludeFrequent.forString(p.text());
+            }
+            throw new IllegalArgumentException("Unsupported token [" + p.currentToken() + "]");
+        }, EXCLUDE_FREQUENT_FIELD, ObjectParser.ValueType.STRING);
+        parser.declareObjectArray(Builder::setRules,
+            (p, c) -> (ignoreUnknownFields ? DetectionRule.LENIENT_PARSER : DetectionRule.STRICT_PARSER).apply(p, c).build(),
+            CUSTOM_RULES_FIELD);
+        parser.declareInt(Builder::setDetectorIndex, DETECTOR_INDEX);
+
+        return parser;
     }
 
     public static final String BY = "by";

+ 16 - 25
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/FilterRef.java

@@ -14,12 +14,9 @@ import org.elasticsearch.common.xcontent.ObjectParser;
 import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
-import org.elasticsearch.xpack.core.ml.MlParserType;
 
 import java.io.IOException;
-import java.util.EnumMap;
 import java.util.Locale;
-import java.util.Map;
 import java.util.Objects;
 
 public class FilterRef implements ToXContentObject, Writeable {
@@ -42,28 +39,22 @@ public class FilterRef implements ToXContentObject, Writeable {
     }
 
     // These parsers follow the pattern that metadata is parsed leniently (to allow for enhancements), whilst config is parsed strictly
-    public static final ConstructingObjectParser<FilterRef, Void> METADATA_PARSER =
-            new ConstructingObjectParser<>(FILTER_REF_FIELD.getPreferredName(), true,
-                    a -> new FilterRef((String) a[0], (FilterType) a[1]));
-    public static final ConstructingObjectParser<FilterRef, Void> CONFIG_PARSER =
-            new ConstructingObjectParser<>(FILTER_REF_FIELD.getPreferredName(), false,
-                    a -> new FilterRef((String) a[0], (FilterType) a[1]));
-    public static final Map<MlParserType, ConstructingObjectParser<FilterRef, Void>> PARSERS = new EnumMap<>(MlParserType.class);
-
-    static {
-        PARSERS.put(MlParserType.METADATA, METADATA_PARSER);
-        PARSERS.put(MlParserType.CONFIG, CONFIG_PARSER);
-        for (MlParserType parserType : MlParserType.values()) {
-            ConstructingObjectParser<FilterRef, Void> parser = PARSERS.get(parserType);
-            assert parser != null;
-            parser.declareString(ConstructingObjectParser.constructorArg(), FILTER_ID);
-            parser.declareField(ConstructingObjectParser.optionalConstructorArg(), p -> {
-                if (p.currentToken() == XContentParser.Token.VALUE_STRING) {
-                    return FilterType.fromString(p.text());
-                }
-                throw new IllegalArgumentException("Unsupported token [" + p.currentToken() + "]");
-            }, FILTER_TYPE, ObjectParser.ValueType.STRING);
-        }
+    public static final ConstructingObjectParser<FilterRef, Void> LENIENT_PARSER = createParser(true);
+    public static final ConstructingObjectParser<FilterRef, Void> STRICT_PARSER = createParser(false);
+
+    private static ConstructingObjectParser<FilterRef, Void> createParser(boolean ignoreUnknownFields) {
+        ConstructingObjectParser<FilterRef, Void> parser = new ConstructingObjectParser<>(FILTER_REF_FIELD.getPreferredName(),
+            ignoreUnknownFields, a -> new FilterRef((String) a[0], (FilterType) a[1]));
+
+        parser.declareString(ConstructingObjectParser.constructorArg(), FILTER_ID);
+        parser.declareField(ConstructingObjectParser.optionalConstructorArg(), p -> {
+            if (p.currentToken() == XContentParser.Token.VALUE_STRING) {
+                return FilterType.fromString(p.text());
+            }
+            throw new IllegalArgumentException("Unsupported token [" + p.currentToken() + "]");
+        }, FILTER_TYPE, ObjectParser.ValueType.STRING);
+
+        return parser;
     }
 
     private final String filterId;

+ 58 - 59
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/Job.java

@@ -21,7 +21,6 @@ import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
 import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser.Token;
-import org.elasticsearch.xpack.core.ml.MlParserType;
 import org.elasticsearch.xpack.core.ml.job.messages.Messages;
 import org.elasticsearch.xpack.core.ml.job.persistence.AnomalyDetectorsIndexFields;
 import org.elasticsearch.xpack.core.ml.job.process.autodetect.state.DataCounts;
@@ -34,7 +33,6 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
-import java.util.EnumMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -85,69 +83,70 @@ public class Job extends AbstractDiffable<Job> implements Writeable, ToXContentO
     public static final ParseField RESULTS_FIELD = new ParseField("jobs");
 
     // These parsers follow the pattern that metadata is parsed leniently (to allow for enhancements), whilst config is parsed strictly
-    public static final ObjectParser<Builder, Void> METADATA_PARSER = new ObjectParser<>("job_details", true, Builder::new);
-    public static final ObjectParser<Builder, Void> CONFIG_PARSER = new ObjectParser<>("job_details", false, Builder::new);
-    public static final Map<MlParserType, ObjectParser<Builder, Void>> PARSERS = new EnumMap<>(MlParserType.class);
+    public static final ObjectParser<Builder, Void> LENIENT_PARSER = createParser(true);
+    public static final ObjectParser<Builder, Void> STRICT_PARSER = createParser(false);
 
     public static final TimeValue MIN_BACKGROUND_PERSIST_INTERVAL = TimeValue.timeValueHours(1);
     public static final ByteSizeValue PROCESS_MEMORY_OVERHEAD = new ByteSizeValue(100, ByteSizeUnit.MB);
 
     public static final long DEFAULT_MODEL_SNAPSHOT_RETENTION_DAYS = 1;
 
-    static {
-        PARSERS.put(MlParserType.METADATA, METADATA_PARSER);
-        PARSERS.put(MlParserType.CONFIG, CONFIG_PARSER);
-        for (MlParserType parserType : MlParserType.values()) {
-            ObjectParser<Builder, Void> parser = PARSERS.get(parserType);
-            assert parser != null;
-            parser.declareString(Builder::setId, ID);
-            parser.declareString(Builder::setJobType, JOB_TYPE);
-            parser.declareString(Builder::setJobVersion, JOB_VERSION);
-            parser.declareStringArray(Builder::setGroups, GROUPS);
-            parser.declareStringOrNull(Builder::setDescription, DESCRIPTION);
-            parser.declareField(Builder::setCreateTime, p -> {
-                if (p.currentToken() == Token.VALUE_NUMBER) {
-                    return new Date(p.longValue());
-                } else if (p.currentToken() == Token.VALUE_STRING) {
-                    return new Date(TimeUtils.dateStringToEpoch(p.text()));
-                }
-                throw new IllegalArgumentException("unexpected token [" + p.currentToken() +
-                        "] for [" + CREATE_TIME.getPreferredName() + "]");
-            }, CREATE_TIME, ValueType.VALUE);
-            parser.declareField(Builder::setFinishedTime, p -> {
-                if (p.currentToken() == Token.VALUE_NUMBER) {
-                    return new Date(p.longValue());
-                } else if (p.currentToken() == Token.VALUE_STRING) {
-                    return new Date(TimeUtils.dateStringToEpoch(p.text()));
-                }
-                throw new IllegalArgumentException(
-                        "unexpected token [" + p.currentToken() + "] for [" + FINISHED_TIME.getPreferredName() + "]");
-            }, FINISHED_TIME, ValueType.VALUE);
-            parser.declareField(Builder::setLastDataTime, p -> {
-                if (p.currentToken() == Token.VALUE_NUMBER) {
-                    return new Date(p.longValue());
-                } else if (p.currentToken() == Token.VALUE_STRING) {
-                    return new Date(TimeUtils.dateStringToEpoch(p.text()));
-                }
-                throw new IllegalArgumentException(
-                        "unexpected token [" + p.currentToken() + "] for [" + LAST_DATA_TIME.getPreferredName() + "]");
-            }, LAST_DATA_TIME, ValueType.VALUE);
-            parser.declareLong(Builder::setEstablishedModelMemory, ESTABLISHED_MODEL_MEMORY);
-            parser.declareObject(Builder::setAnalysisConfig, AnalysisConfig.PARSERS.get(parserType), ANALYSIS_CONFIG);
-            parser.declareObject(Builder::setAnalysisLimits, AnalysisLimits.PARSERS.get(parserType), ANALYSIS_LIMITS);
-            parser.declareObject(Builder::setDataDescription, DataDescription.PARSERS.get(parserType), DATA_DESCRIPTION);
-            parser.declareObject(Builder::setModelPlotConfig, ModelPlotConfig.PARSERS.get(parserType), MODEL_PLOT_CONFIG);
-            parser.declareLong(Builder::setRenormalizationWindowDays, RENORMALIZATION_WINDOW_DAYS);
-            parser.declareString((builder, val) -> builder.setBackgroundPersistInterval(
-                    TimeValue.parseTimeValue(val, BACKGROUND_PERSIST_INTERVAL.getPreferredName())), BACKGROUND_PERSIST_INTERVAL);
-            parser.declareLong(Builder::setResultsRetentionDays, RESULTS_RETENTION_DAYS);
-            parser.declareLong(Builder::setModelSnapshotRetentionDays, MODEL_SNAPSHOT_RETENTION_DAYS);
-            parser.declareField(Builder::setCustomSettings, (p, c) -> p.map(), CUSTOM_SETTINGS, ValueType.OBJECT);
-            parser.declareStringOrNull(Builder::setModelSnapshotId, MODEL_SNAPSHOT_ID);
-            parser.declareStringOrNull(Builder::setModelSnapshotMinVersion, MODEL_SNAPSHOT_MIN_VERSION);
-            parser.declareString(Builder::setResultsIndexName, RESULTS_INDEX_NAME);
-            parser.declareBoolean(Builder::setDeleted, DELETED);
-        }
+    private static ObjectParser<Builder, Void> createParser(boolean ignoreUnknownFields) {
+        ObjectParser<Builder, Void> parser = new ObjectParser<>("job_details", ignoreUnknownFields, Builder::new);
+
+        parser.declareString(Builder::setId, ID);
+        parser.declareString(Builder::setJobType, JOB_TYPE);
+        parser.declareString(Builder::setJobVersion, JOB_VERSION);
+        parser.declareStringArray(Builder::setGroups, GROUPS);
+        parser.declareStringOrNull(Builder::setDescription, DESCRIPTION);
+        parser.declareField(Builder::setCreateTime, p -> {
+            if (p.currentToken() == Token.VALUE_NUMBER) {
+                return new Date(p.longValue());
+            } else if (p.currentToken() == Token.VALUE_STRING) {
+                return new Date(TimeUtils.dateStringToEpoch(p.text()));
+            }
+            throw new IllegalArgumentException("unexpected token [" + p.currentToken() +
+                "] for [" + CREATE_TIME.getPreferredName() + "]");
+        }, CREATE_TIME, ValueType.VALUE);
+        parser.declareField(Builder::setFinishedTime, p -> {
+            if (p.currentToken() == Token.VALUE_NUMBER) {
+                return new Date(p.longValue());
+            } else if (p.currentToken() == Token.VALUE_STRING) {
+                return new Date(TimeUtils.dateStringToEpoch(p.text()));
+            }
+            throw new IllegalArgumentException(
+                "unexpected token [" + p.currentToken() + "] for [" + FINISHED_TIME.getPreferredName() + "]");
+        }, FINISHED_TIME, ValueType.VALUE);
+        parser.declareField(Builder::setLastDataTime, p -> {
+            if (p.currentToken() == Token.VALUE_NUMBER) {
+                return new Date(p.longValue());
+            } else if (p.currentToken() == Token.VALUE_STRING) {
+                return new Date(TimeUtils.dateStringToEpoch(p.text()));
+            }
+            throw new IllegalArgumentException(
+                "unexpected token [" + p.currentToken() + "] for [" + LAST_DATA_TIME.getPreferredName() + "]");
+        }, LAST_DATA_TIME, ValueType.VALUE);
+        parser.declareLong(Builder::setEstablishedModelMemory, ESTABLISHED_MODEL_MEMORY);
+        parser.declareObject(Builder::setAnalysisConfig, ignoreUnknownFields ? AnalysisConfig.LENIENT_PARSER : AnalysisConfig.STRICT_PARSER,
+            ANALYSIS_CONFIG);
+        parser.declareObject(Builder::setAnalysisLimits, ignoreUnknownFields ? AnalysisLimits.LENIENT_PARSER : AnalysisLimits.STRICT_PARSER,
+            ANALYSIS_LIMITS);
+        parser.declareObject(Builder::setDataDescription,
+            ignoreUnknownFields ? DataDescription.LENIENT_PARSER : DataDescription.STRICT_PARSER, DATA_DESCRIPTION);
+        parser.declareObject(Builder::setModelPlotConfig,
+            ignoreUnknownFields ? ModelPlotConfig.LENIENT_PARSER : ModelPlotConfig.STRICT_PARSER, MODEL_PLOT_CONFIG);
+        parser.declareLong(Builder::setRenormalizationWindowDays, RENORMALIZATION_WINDOW_DAYS);
+        parser.declareString((builder, val) -> builder.setBackgroundPersistInterval(
+            TimeValue.parseTimeValue(val, BACKGROUND_PERSIST_INTERVAL.getPreferredName())), BACKGROUND_PERSIST_INTERVAL);
+        parser.declareLong(Builder::setResultsRetentionDays, RESULTS_RETENTION_DAYS);
+        parser.declareLong(Builder::setModelSnapshotRetentionDays, MODEL_SNAPSHOT_RETENTION_DAYS);
+        parser.declareField(Builder::setCustomSettings, (p, c) -> p.map(), CUSTOM_SETTINGS, ValueType.OBJECT);
+        parser.declareStringOrNull(Builder::setModelSnapshotId, MODEL_SNAPSHOT_ID);
+        parser.declareStringOrNull(Builder::setModelSnapshotMinVersion, MODEL_SNAPSHOT_MIN_VERSION);
+        parser.declareString(Builder::setResultsIndexName, RESULTS_INDEX_NAME);
+        parser.declareBoolean(Builder::setDeleted, DELETED);
+
+        return parser;
     }
 
     private final String jobId;

+ 3 - 3
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/JobUpdate.java

@@ -44,8 +44,8 @@ public class JobUpdate implements Writeable, ToXContentObject {
             parser.declareStringArray(Builder::setGroups, Job.GROUPS);
             parser.declareStringOrNull(Builder::setDescription, Job.DESCRIPTION);
             parser.declareObjectArray(Builder::setDetectorUpdates, DetectorUpdate.PARSER, DETECTORS);
-            parser.declareObject(Builder::setModelPlotConfig, ModelPlotConfig.CONFIG_PARSER, Job.MODEL_PLOT_CONFIG);
-            parser.declareObject(Builder::setAnalysisLimits, AnalysisLimits.CONFIG_PARSER, Job.ANALYSIS_LIMITS);
+            parser.declareObject(Builder::setModelPlotConfig, ModelPlotConfig.STRICT_PARSER, Job.MODEL_PLOT_CONFIG);
+            parser.declareObject(Builder::setAnalysisLimits, AnalysisLimits.STRICT_PARSER, Job.ANALYSIS_LIMITS);
             parser.declareString((builder, val) -> builder.setBackgroundPersistInterval(
                     TimeValue.parseTimeValue(val, Job.BACKGROUND_PERSIST_INTERVAL.getPreferredName())), Job.BACKGROUND_PERSIST_INTERVAL);
             parser.declareLong(Builder::setRenormalizationWindowDays, Job.RENORMALIZATION_WINDOW_DAYS);
@@ -533,7 +533,7 @@ public class JobUpdate implements Writeable, ToXContentObject {
             PARSER.declareInt(ConstructingObjectParser.optionalConstructorArg(), Detector.DETECTOR_INDEX);
             PARSER.declareStringOrNull(ConstructingObjectParser.optionalConstructorArg(), Job.DESCRIPTION);
             PARSER.declareObjectArray(ConstructingObjectParser.optionalConstructorArg(), (parser, parseFieldMatcher) ->
-                    DetectionRule.CONFIG_PARSER.apply(parser, parseFieldMatcher).build(), Detector.CUSTOM_RULES_FIELD);
+                    DetectionRule.STRICT_PARSER.apply(parser, parseFieldMatcher).build(), Detector.CUSTOM_RULES_FIELD);
         }
 
         private int detectorIndex;

+ 11 - 21
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/ModelPlotConfig.java

@@ -12,11 +12,8 @@ import org.elasticsearch.common.io.stream.Writeable;
 import org.elasticsearch.common.xcontent.ConstructingObjectParser;
 import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.xpack.core.ml.MlParserType;
 
 import java.io.IOException;
-import java.util.EnumMap;
-import java.util.Map;
 import java.util.Objects;
 
 public class ModelPlotConfig implements ToXContentObject, Writeable {
@@ -26,24 +23,17 @@ public class ModelPlotConfig implements ToXContentObject, Writeable {
     public static final ParseField TERMS_FIELD = new ParseField("terms");
 
     // These parsers follow the pattern that metadata is parsed leniently (to allow for enhancements), whilst config is parsed strictly
-    public static final ConstructingObjectParser<ModelPlotConfig, Void> METADATA_PARSER =
-            new ConstructingObjectParser<>(TYPE_FIELD.getPreferredName(), true,
-                    a -> new ModelPlotConfig((boolean) a[0], (String) a[1]));
-    public static final ConstructingObjectParser<ModelPlotConfig, Void> CONFIG_PARSER =
-            new ConstructingObjectParser<>(TYPE_FIELD.getPreferredName(), false,
-                    a -> new ModelPlotConfig((boolean) a[0], (String) a[1]));
-    public static final Map<MlParserType, ConstructingObjectParser<ModelPlotConfig, Void>> PARSERS =
-            new EnumMap<>(MlParserType.class);
-
-    static {
-        PARSERS.put(MlParserType.METADATA, METADATA_PARSER);
-        PARSERS.put(MlParserType.CONFIG, CONFIG_PARSER);
-        for (MlParserType parserType : MlParserType.values()) {
-            ConstructingObjectParser<ModelPlotConfig, Void> parser = PARSERS.get(parserType);
-            assert parser != null;
-            parser.declareBoolean(ConstructingObjectParser.constructorArg(), ENABLED_FIELD);
-            parser.declareString(ConstructingObjectParser.optionalConstructorArg(), TERMS_FIELD);
-        }
+    public static final ConstructingObjectParser<ModelPlotConfig, Void> LENIENT_PARSER = createParser(true);
+    public static final ConstructingObjectParser<ModelPlotConfig, Void> STRICT_PARSER = createParser(false);
+
+    private static ConstructingObjectParser<ModelPlotConfig, Void> createParser(boolean ignoreUnknownFields) {
+        ConstructingObjectParser<ModelPlotConfig, Void> parser = new ConstructingObjectParser<>(TYPE_FIELD.getPreferredName(),
+            ignoreUnknownFields, a -> new ModelPlotConfig((boolean) a[0], (String) a[1]));
+
+        parser.declareBoolean(ConstructingObjectParser.constructorArg(), ENABLED_FIELD);
+        parser.declareString(ConstructingObjectParser.optionalConstructorArg(), TERMS_FIELD);
+
+        return parser;
     }
 
     private final boolean enabled;

+ 22 - 32
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/RuleCondition.java

@@ -14,12 +14,9 @@ import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
 import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
-import org.elasticsearch.xpack.core.ml.MlParserType;
 
 import java.io.IOException;
-import java.util.EnumMap;
 import java.util.Locale;
-import java.util.Map;
 import java.util.Objects;
 
 public class RuleCondition implements ToXContentObject, Writeable {
@@ -30,35 +27,28 @@ public class RuleCondition implements ToXContentObject, Writeable {
     public static final ParseField VALUE_FIELD = new ParseField("value");
 
     // These parsers follow the pattern that metadata is parsed leniently (to allow for enhancements), whilst config is parsed strictly
-    public static final ConstructingObjectParser<RuleCondition, Void> METADATA_PARSER =
-            new ConstructingObjectParser<>(RULE_CONDITION_FIELD.getPreferredName(), true,
-                    a -> new RuleCondition((AppliesTo) a[0], (Operator) a[1], (double) a[2]));
-    public static final ConstructingObjectParser<RuleCondition, Void> CONFIG_PARSER =
-            new ConstructingObjectParser<>(RULE_CONDITION_FIELD.getPreferredName(), false,
-                    a -> new RuleCondition((AppliesTo) a[0], (Operator) a[1], (double) a[2]));
-    public static final Map<MlParserType, ConstructingObjectParser<RuleCondition, Void>> PARSERS =
-            new EnumMap<>(MlParserType.class);
-
-    static {
-        PARSERS.put(MlParserType.METADATA, METADATA_PARSER);
-        PARSERS.put(MlParserType.CONFIG, CONFIG_PARSER);
-        for (MlParserType parserType : MlParserType.values()) {
-            ConstructingObjectParser<RuleCondition, Void> parser = PARSERS.get(parserType);
-            assert parser != null;
-            parser.declareField(ConstructingObjectParser.constructorArg(), p -> {
-                if (p.currentToken() == XContentParser.Token.VALUE_STRING) {
-                    return AppliesTo.fromString(p.text());
-                }
-                throw new IllegalArgumentException("Unsupported token [" + p.currentToken() + "]");
-            }, APPLIES_TO_FIELD, ValueType.STRING);
-            parser.declareField(ConstructingObjectParser.constructorArg(), p -> {
-                if (p.currentToken() == XContentParser.Token.VALUE_STRING) {
-                    return Operator.fromString(p.text());
-                }
-                throw new IllegalArgumentException("Unsupported token [" + p.currentToken() + "]");
-            }, Operator.OPERATOR_FIELD, ValueType.STRING);
-            parser.declareDouble(ConstructingObjectParser.constructorArg(), VALUE_FIELD);
-        }
+    public static final ConstructingObjectParser<RuleCondition, Void> LENIENT_PARSER = createParser(true);
+    public static final ConstructingObjectParser<RuleCondition, Void> STRICT_PARSER = createParser(false);
+
+    private static ConstructingObjectParser<RuleCondition, Void> createParser(boolean ignoreUnknownFields) {
+        ConstructingObjectParser<RuleCondition, Void> parser = new ConstructingObjectParser<>(RULE_CONDITION_FIELD.getPreferredName(),
+            ignoreUnknownFields, a -> new RuleCondition((AppliesTo) a[0], (Operator) a[1], (double) a[2]));
+
+        parser.declareField(ConstructingObjectParser.constructorArg(), p -> {
+            if (p.currentToken() == XContentParser.Token.VALUE_STRING) {
+                return AppliesTo.fromString(p.text());
+            }
+            throw new IllegalArgumentException("Unsupported token [" + p.currentToken() + "]");
+        }, APPLIES_TO_FIELD, ValueType.STRING);
+        parser.declareField(ConstructingObjectParser.constructorArg(), p -> {
+            if (p.currentToken() == XContentParser.Token.VALUE_STRING) {
+                return Operator.fromString(p.text());
+            }
+            throw new IllegalArgumentException("Unsupported token [" + p.currentToken() + "]");
+        }, Operator.OPERATOR_FIELD, ValueType.STRING);
+        parser.declareDouble(ConstructingObjectParser.constructorArg(), VALUE_FIELD);
+
+        return parser;
     }
 
     private final AppliesTo appliesTo;

+ 3 - 3
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/RuleScope.java

@@ -17,7 +17,6 @@ import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentFactory;
 import org.elasticsearch.common.xcontent.XContentParser;
-import org.elasticsearch.xpack.core.ml.MlParserType;
 import org.elasticsearch.xpack.core.ml.job.messages.Messages;
 import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
 
@@ -32,13 +31,14 @@ import java.util.stream.Collectors;
 
 public class RuleScope implements ToXContentObject, Writeable {
 
-    public static ContextParser<Void, RuleScope> parser(MlParserType parserType) {
+    public static ContextParser<Void, RuleScope> parser(boolean ignoreUnknownFields) {
         return (p, c) -> {
             Map<String, Object> unparsedScope = p.map();
             if (unparsedScope.isEmpty()) {
                 return new RuleScope();
             }
-            ConstructingObjectParser<FilterRef, Void> filterRefParser = FilterRef.PARSERS.get(parserType);
+            ConstructingObjectParser<FilterRef, Void> filterRefParser =
+                ignoreUnknownFields ? FilterRef.LENIENT_PARSER : FilterRef.STRICT_PARSER;
             Map<String, FilterRef> scope = new HashMap<>();
             for (Map.Entry<String, Object> entry : unparsedScope.entrySet()) {
                 try (XContentBuilder builder = XContentFactory.jsonBuilder()) {

+ 1 - 1
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/datafeed/ChunkingConfigTests.java

@@ -32,7 +32,7 @@ public class ChunkingConfigTests extends AbstractSerializingTestCase<ChunkingCon
 
     @Override
     protected ChunkingConfig doParseInstance(XContentParser parser) {
-        return ChunkingConfig.CONFIG_PARSER.apply(parser, null);
+        return ChunkingConfig.STRICT_PARSER.apply(parser, null);
     }
 
     public void testConstructorGivenAutoAndTimeSpan() {

+ 3 - 3
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/datafeed/DatafeedConfigTests.java

@@ -140,7 +140,7 @@ public class DatafeedConfigTests extends AbstractSerializingTestCase<DatafeedCon
 
     @Override
     protected DatafeedConfig doParseInstance(XContentParser parser) {
-        return DatafeedConfig.CONFIG_PARSER.apply(parser, null).build();
+        return DatafeedConfig.STRICT_PARSER.apply(parser, null).build();
     }
 
     private static final String FUTURE_DATAFEED = "{\n" +
@@ -156,7 +156,7 @@ public class DatafeedConfigTests extends AbstractSerializingTestCase<DatafeedCon
         XContentParser parser = XContentFactory.xContent(XContentType.JSON)
                 .createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, FUTURE_DATAFEED);
         XContentParseException e = expectThrows(XContentParseException.class,
-                () -> DatafeedConfig.CONFIG_PARSER.apply(parser, null).build());
+                () -> DatafeedConfig.STRICT_PARSER.apply(parser, null).build());
         assertEquals("[6:5] [datafeed_config] unknown field [tomorrows_technology_today], parser not found", e.getMessage());
     }
 
@@ -164,7 +164,7 @@ public class DatafeedConfigTests extends AbstractSerializingTestCase<DatafeedCon
         XContentParser parser = XContentFactory.xContent(XContentType.JSON)
                 .createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, FUTURE_DATAFEED);
         // Unlike the config version of this test, the metadata parser should tolerate the unknown future field
-        assertNotNull(DatafeedConfig.METADATA_PARSER.apply(parser, null).build());
+        assertNotNull(DatafeedConfig.LENIENT_PARSER.apply(parser, null).build());
     }
 
     public void testCopyConstructor() {

+ 1 - 1
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/job/config/AnalysisConfigTests.java

@@ -120,7 +120,7 @@ public class AnalysisConfigTests extends AbstractSerializingTestCase<AnalysisCon
 
     @Override
     protected AnalysisConfig doParseInstance(XContentParser parser) {
-        return AnalysisConfig.CONFIG_PARSER.apply(parser, null).build();
+        return AnalysisConfig.STRICT_PARSER.apply(parser, null).build();
     }
 
     public void testFieldConfiguration_singleDetector_notPreSummarised() {

+ 9 - 9
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/job/config/AnalysisLimitsTests.java

@@ -41,14 +41,14 @@ public class AnalysisLimitsTests extends AbstractSerializingTestCase<AnalysisLim
 
     @Override
     protected AnalysisLimits doParseInstance(XContentParser parser) {
-        return AnalysisLimits.CONFIG_PARSER.apply(parser, null);
+        return AnalysisLimits.STRICT_PARSER.apply(parser, null);
     }
 
     public void testParseModelMemoryLimitGivenNegativeNumber() throws IOException {
         String json = "{\"model_memory_limit\": -1}";
         XContentParser parser = XContentFactory.xContent(XContentType.JSON)
                 .createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, json);
-        XContentParseException e = expectThrows(XContentParseException.class, () -> AnalysisLimits.CONFIG_PARSER.apply(parser, null));
+        XContentParseException e = expectThrows(XContentParseException.class, () -> AnalysisLimits.STRICT_PARSER.apply(parser, null));
         assertThat(ExceptionsHelper.detailedMessage(e), containsString("model_memory_limit must be at least 1 MiB. Value = -1"));
     }
 
@@ -56,7 +56,7 @@ public class AnalysisLimitsTests extends AbstractSerializingTestCase<AnalysisLim
         String json = "{\"model_memory_limit\": 0}";
         XContentParser parser = XContentFactory.xContent(XContentType.JSON)
                 .createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, json);
-        XContentParseException e = expectThrows(XContentParseException.class, () -> AnalysisLimits.CONFIG_PARSER.apply(parser, null));
+        XContentParseException e = expectThrows(XContentParseException.class, () -> AnalysisLimits.STRICT_PARSER.apply(parser, null));
         assertThat(ExceptionsHelper.detailedMessage(e), containsString("model_memory_limit must be at least 1 MiB. Value = 0"));
     }
 
@@ -65,7 +65,7 @@ public class AnalysisLimitsTests extends AbstractSerializingTestCase<AnalysisLim
         XContentParser parser = XContentFactory.xContent(XContentType.JSON)
                 .createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, json);
 
-        AnalysisLimits limits = AnalysisLimits.CONFIG_PARSER.apply(parser, null);
+        AnalysisLimits limits = AnalysisLimits.STRICT_PARSER.apply(parser, null);
 
         assertThat(limits.getModelMemoryLimit(), equalTo(2048L));
     }
@@ -74,7 +74,7 @@ public class AnalysisLimitsTests extends AbstractSerializingTestCase<AnalysisLim
         String json = "{\"model_memory_limit\":\"-4MB\"}";
         XContentParser parser = XContentFactory.xContent(XContentType.JSON)
                 .createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, json);
-        XContentParseException e = expectThrows(XContentParseException.class, () -> AnalysisLimits.CONFIG_PARSER.apply(parser, null));
+        XContentParseException e = expectThrows(XContentParseException.class, () -> AnalysisLimits.STRICT_PARSER.apply(parser, null));
         assertThat(ExceptionsHelper.detailedMessage(e), containsString("Values less than -1 bytes are not supported: -4mb"));
     }
 
@@ -82,7 +82,7 @@ public class AnalysisLimitsTests extends AbstractSerializingTestCase<AnalysisLim
         String json = "{\"model_memory_limit\":\"0MB\"}";
         XContentParser parser = XContentFactory.xContent(XContentType.JSON)
                 .createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, json);
-        XContentParseException e = expectThrows(XContentParseException.class, () -> AnalysisLimits.CONFIG_PARSER.apply(parser, null));
+        XContentParseException e = expectThrows(XContentParseException.class, () -> AnalysisLimits.STRICT_PARSER.apply(parser, null));
         assertThat(ExceptionsHelper.detailedMessage(e), containsString("model_memory_limit must be at least 1 MiB. Value = 0"));
     }
 
@@ -90,7 +90,7 @@ public class AnalysisLimitsTests extends AbstractSerializingTestCase<AnalysisLim
         String json = "{\"model_memory_limit\":\"1000Kb\"}";
         XContentParser parser = XContentFactory.xContent(XContentType.JSON)
                 .createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, json);
-        XContentParseException e = expectThrows(XContentParseException.class, () -> AnalysisLimits.CONFIG_PARSER.apply(parser, null));
+        XContentParseException e = expectThrows(XContentParseException.class, () -> AnalysisLimits.STRICT_PARSER.apply(parser, null));
         assertThat(ExceptionsHelper.detailedMessage(e), containsString("model_memory_limit must be at least 1 MiB. Value = 0"));
     }
 
@@ -99,7 +99,7 @@ public class AnalysisLimitsTests extends AbstractSerializingTestCase<AnalysisLim
         XContentParser parser = XContentFactory.xContent(XContentType.JSON)
                 .createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, json);
 
-        AnalysisLimits limits = AnalysisLimits.CONFIG_PARSER.apply(parser, null);
+        AnalysisLimits limits = AnalysisLimits.STRICT_PARSER.apply(parser, null);
 
         assertThat(limits.getModelMemoryLimit(), equalTo(4096L));
     }
@@ -109,7 +109,7 @@ public class AnalysisLimitsTests extends AbstractSerializingTestCase<AnalysisLim
         XContentParser parser = XContentFactory.xContent(XContentType.JSON)
                 .createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, json);
 
-        AnalysisLimits limits = AnalysisLimits.CONFIG_PARSER.apply(parser, null);
+        AnalysisLimits limits = AnalysisLimits.STRICT_PARSER.apply(parser, null);
 
         assertThat(limits.getModelMemoryLimit(), equalTo(1L));
     }

+ 4 - 4
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/job/config/DataDescriptionTests.java

@@ -212,7 +212,7 @@ public class DataDescriptionTests extends AbstractSerializingTestCase<DataDescri
         XContentParser parser = JsonXContent.jsonXContent
                 .createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, json.streamInput());
         XContentParseException ex = expectThrows(XContentParseException.class,
-                () -> DataDescription.CONFIG_PARSER.apply(parser, null));
+                () -> DataDescription.STRICT_PARSER.apply(parser, null));
         assertThat(ex.getMessage(), containsString("[data_description] failed to parse field [format]"));
         Throwable cause = ex.getCause();
         assertNotNull(cause);
@@ -226,7 +226,7 @@ public class DataDescriptionTests extends AbstractSerializingTestCase<DataDescri
         XContentParser parser = JsonXContent.jsonXContent
                 .createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, json.streamInput());
         XContentParseException ex = expectThrows(XContentParseException.class,
-                () -> DataDescription.CONFIG_PARSER.apply(parser, null));
+                () -> DataDescription.STRICT_PARSER.apply(parser, null));
         assertThat(ex.getMessage(), containsString("[data_description] failed to parse field [field_delimiter]"));
         Throwable cause = ex.getCause();
         assertNotNull(cause);
@@ -240,7 +240,7 @@ public class DataDescriptionTests extends AbstractSerializingTestCase<DataDescri
         XContentParser parser = JsonXContent.jsonXContent
                 .createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, json.streamInput());
         XContentParseException ex = expectThrows(XContentParseException.class,
-                () -> DataDescription.CONFIG_PARSER.apply(parser, null));
+                () -> DataDescription.STRICT_PARSER.apply(parser, null));
         assertThat(ex.getMessage(), containsString("[data_description] failed to parse field [quote_character]"));
         Throwable cause = ex.getCause();
         assertNotNull(cause);
@@ -284,7 +284,7 @@ public class DataDescriptionTests extends AbstractSerializingTestCase<DataDescri
 
     @Override
     protected DataDescription doParseInstance(XContentParser parser) {
-        return DataDescription.CONFIG_PARSER.apply(parser, null).build();
+        return DataDescription.STRICT_PARSER.apply(parser, null).build();
     }
 
     protected DataDescription mutateInstance(DataDescription instance) throws java.io.IOException {

+ 1 - 1
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/job/config/DetectionRuleTests.java

@@ -90,7 +90,7 @@ public class DetectionRuleTests extends AbstractSerializingTestCase<DetectionRul
 
     @Override
     protected DetectionRule doParseInstance(XContentParser parser) {
-        return DetectionRule.CONFIG_PARSER.apply(parser, null).build();
+        return DetectionRule.STRICT_PARSER.apply(parser, null).build();
     }
 
     @Override

+ 1 - 1
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/job/config/DetectorTests.java

@@ -182,7 +182,7 @@ public class DetectorTests extends AbstractSerializingTestCase<Detector> {
 
     @Override
     protected Detector doParseInstance(XContentParser parser) {
-        return Detector.CONFIG_PARSER.apply(parser, null).build();
+        return Detector.STRICT_PARSER.apply(parser, null).build();
     }
 
     public void testVerifyFieldNames_givenInvalidChars() {

+ 2 - 2
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/job/config/FilterRefTests.java

@@ -20,11 +20,11 @@ public class FilterRefTests extends AbstractSerializingTestCase<FilterRef> {
 
     @Override
     protected FilterRef doParseInstance(XContentParser parser) throws IOException {
-        return FilterRef.CONFIG_PARSER.parse(parser, null);
+        return FilterRef.STRICT_PARSER.parse(parser, null);
     }
 
     @Override
     protected Writeable.Reader<FilterRef> instanceReader() {
         return FilterRef::new;
     }
-}
+}

+ 3 - 3
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/job/config/JobTests.java

@@ -74,14 +74,14 @@ public class JobTests extends AbstractSerializingTestCase<Job> {
 
     @Override
     protected Job doParseInstance(XContentParser parser) {
-        return Job.CONFIG_PARSER.apply(parser, null).build();
+        return Job.STRICT_PARSER.apply(parser, null).build();
     }
 
     public void testFutureConfigParse() throws IOException {
         XContentParser parser = XContentFactory.xContent(XContentType.JSON)
                 .createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, FUTURE_JOB);
         XContentParseException e = expectThrows(XContentParseException.class,
-                () -> Job.CONFIG_PARSER.apply(parser, null).build());
+                () -> Job.STRICT_PARSER.apply(parser, null).build());
         assertEquals("[4:5] [job_details] unknown field [tomorrows_technology_today], parser not found", e.getMessage());
     }
 
@@ -89,7 +89,7 @@ public class JobTests extends AbstractSerializingTestCase<Job> {
         XContentParser parser = XContentFactory.xContent(XContentType.JSON)
                 .createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, FUTURE_JOB);
         // Unlike the config version of this test, the metadata parser should tolerate the unknown future field
-        assertNotNull(Job.METADATA_PARSER.apply(parser, null).build());
+        assertNotNull(Job.LENIENT_PARSER.apply(parser, null).build());
     }
 
     public void testConstructor_GivenEmptyJobConfiguration() {

+ 1 - 1
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/job/config/ModelPlotConfigTests.java

@@ -31,6 +31,6 @@ public class ModelPlotConfigTests extends AbstractSerializingTestCase<ModelPlotC
 
     @Override
     protected ModelPlotConfig doParseInstance(XContentParser parser) {
-        return ModelPlotConfig.CONFIG_PARSER.apply(parser, null);
+        return ModelPlotConfig.STRICT_PARSER.apply(parser, null);
     }
 }

+ 1 - 1
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/job/config/RuleConditionTests.java

@@ -29,7 +29,7 @@ public class RuleConditionTests extends AbstractSerializingTestCase<RuleConditio
 
     @Override
     protected RuleCondition doParseInstance(XContentParser parser) {
-        return RuleCondition.CONFIG_PARSER.apply(parser, null);
+        return RuleCondition.STRICT_PARSER.apply(parser, null);
     }
 
     public void testEqualsGivenSameObject() {

+ 1 - 1
x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/MlMetadataTests.java

@@ -79,7 +79,7 @@ public class MlMetadataTests extends AbstractSerializingTestCase<MlMetadata> {
 
     @Override
     protected MlMetadata doParseInstance(XContentParser parser) {
-        return MlMetadata.METADATA_PARSER.apply(parser, null).build();
+        return MlMetadata.LENIENT_PARSER.apply(parser, null).build();
     }
 
     @Override

+ 1 - 2
x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/job/config/CategorizationAnalyzerConfigTests.java

@@ -9,7 +9,6 @@ import org.elasticsearch.common.io.stream.Writeable;
 import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.test.AbstractSerializingTestCase;
 import org.elasticsearch.xpack.core.ml.job.config.CategorizationAnalyzerConfig;
-import org.elasticsearch.xpack.core.ml.MlParserType;
 
 import java.io.IOException;
 import java.util.HashMap;
@@ -72,7 +71,7 @@ public class CategorizationAnalyzerConfigTests extends AbstractSerializingTestCa
 
     @Override
     protected CategorizationAnalyzerConfig doParseInstance(XContentParser parser) throws IOException {
-        return CategorizationAnalyzerConfig.buildFromXContentObject(parser, MlParserType.CONFIG);
+        return CategorizationAnalyzerConfig.buildFromXContentObject(parser, false);
     }
 
     @Override

+ 1 - 1
x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/job/config/JobBuilderTests.java

@@ -86,6 +86,6 @@ public class JobBuilderTests extends AbstractSerializingTestCase<Job.Builder> {
 
     @Override
     protected Job.Builder doParseInstance(XContentParser parser) {
-        return Job.CONFIG_PARSER.apply(parser, null);
+        return Job.STRICT_PARSER.apply(parser, null);
     }
 }