1919
2020package org .elasticsearch .index .mapper ;
2121
22+ import org .apache .logging .log4j .LogManager ;
23+ import org .apache .logging .log4j .Logger ;
2224import org .elasticsearch .Version ;
2325import org .elasticsearch .common .Explicit ;
2426import org .elasticsearch .common .Nullable ;
27+ import org .elasticsearch .common .Strings ;
28+ import org .elasticsearch .common .logging .DeprecationLogger ;
2529import org .elasticsearch .common .settings .Settings ;
2630import org .elasticsearch .common .time .DateFormatter ;
2731import org .elasticsearch .common .xcontent .ToXContent ;
3438import java .util .Collections ;
3539import java .util .Iterator ;
3640import java .util .List ;
41+ import java .util .Locale ;
3742import java .util .Map ;
3843
3944import static org .elasticsearch .common .xcontent .support .XContentMapValues .nodeBooleanValue ;
4045import static org .elasticsearch .index .mapper .TypeParsers .parseDateTimeFormatter ;
4146
4247public class RootObjectMapper extends ObjectMapper {
4348
49+ private static final Logger LOGGER = LogManager .getLogger (RootObjectMapper .class );
50+ private static final DeprecationLogger DEPRECATION_LOGGER = new DeprecationLogger (LOGGER );
51+
4452 public static class Defaults {
4553 public static final DateFormatter [] DYNAMIC_DATE_TIME_FORMATTERS =
4654 new DateFormatter []{
@@ -130,7 +138,7 @@ public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext
130138 String fieldName = entry .getKey ();
131139 Object fieldNode = entry .getValue ();
132140 if (parseObjectOrDocumentTypeProperties (fieldName , fieldNode , parserContext , builder )
133- || processField (builder , fieldName , fieldNode , parserContext . indexVersionCreated () )) {
141+ || processField (builder , fieldName , fieldNode , parserContext )) {
134142 iterator .remove ();
135143 }
136144 }
@@ -139,7 +147,7 @@ public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext
139147
140148 @ SuppressWarnings ("unchecked" )
141149 protected boolean processField (RootObjectMapper .Builder builder , String fieldName , Object fieldNode ,
142- Version indexVersionCreated ) {
150+ ParserContext parserContext ) {
143151 if (fieldName .equals ("date_formats" ) || fieldName .equals ("dynamic_date_formats" )) {
144152 if (fieldNode instanceof List ) {
145153 List <DateFormatter > formatters = new ArrayList <>();
@@ -163,7 +171,7 @@ protected boolean processField(RootObjectMapper.Builder builder, String fieldNam
163171 "template_1" : {
164172 "match" : "*_test",
165173 "match_mapping_type" : "string",
166- "mapping" : { "type" : "string ", "store" : "yes" }
174+ "mapping" : { "type" : "keyword ", "store" : "yes" }
167175 }
168176 }
169177 ]
@@ -183,6 +191,7 @@ protected boolean processField(RootObjectMapper.Builder builder, String fieldNam
183191 Map <String , Object > templateParams = (Map <String , Object >) entry .getValue ();
184192 DynamicTemplate template = DynamicTemplate .parse (templateName , templateParams );
185193 if (template != null ) {
194+ validateDynamicTemplate (parserContext , template );
186195 templates .add (template );
187196 }
188197 }
@@ -333,4 +342,114 @@ protected void doXContent(XContentBuilder builder, ToXContent.Params params) thr
333342 builder .field ("numeric_detection" , numericDetection .value ());
334343 }
335344 }
345+
346+ private static void validateDynamicTemplate (Mapper .TypeParser .ParserContext parserContext ,
347+ DynamicTemplate dynamicTemplate ) {
348+
349+ if (containsSnippet (dynamicTemplate .getMapping (), "{name}" )) {
350+ // Can't validate template, because field names can't be guessed up front.
351+ return ;
352+ }
353+
354+ final XContentFieldType [] types ;
355+ if (dynamicTemplate .getXContentFieldType () != null ) {
356+ types = new XContentFieldType []{dynamicTemplate .getXContentFieldType ()};
357+ } else {
358+ types = XContentFieldType .values ();
359+ }
360+
361+ Exception lastError = null ;
362+ boolean dynamicTemplateInvalid = true ;
363+
364+ for (XContentFieldType contentFieldType : types ) {
365+ String defaultDynamicType = contentFieldType .defaultMappingType ();
366+ String mappingType = dynamicTemplate .mappingType (defaultDynamicType );
367+ Mapper .TypeParser typeParser = parserContext .typeParser (mappingType );
368+ if (typeParser == null ) {
369+ lastError = new IllegalArgumentException ("No mapper found for type [" + mappingType + "]" );
370+ continue ;
371+ }
372+
373+ Map <String , Object > fieldTypeConfig = dynamicTemplate .mappingForName ("__dummy__" , defaultDynamicType );
374+ fieldTypeConfig .remove ("type" );
375+ try {
376+ Mapper .Builder <?, ?> dummyBuilder = typeParser .parse ("__dummy__" , fieldTypeConfig , parserContext );
377+ if (fieldTypeConfig .isEmpty ()) {
378+ Settings indexSettings = parserContext .mapperService ().getIndexSettings ().getSettings ();
379+ BuilderContext builderContext = new BuilderContext (indexSettings , new ContentPath (1 ));
380+ dummyBuilder .build (builderContext );
381+ dynamicTemplateInvalid = false ;
382+ break ;
383+ } else {
384+ lastError = new IllegalArgumentException ("Unused mapping attributes [" + fieldTypeConfig + "]" );
385+ }
386+ } catch (Exception e ) {
387+ lastError = e ;
388+ }
389+ }
390+
391+ final boolean failInvalidDynamicTemplates = parserContext .indexVersionCreated ().onOrAfter (Version .V_8_0_0 );
392+ if (dynamicTemplateInvalid ) {
393+ String message = String .format (Locale .ROOT , "dynamic template [%s] has invalid content [%s]" ,
394+ dynamicTemplate .getName (), Strings .toString (dynamicTemplate ));
395+ if (failInvalidDynamicTemplates ) {
396+ throw new IllegalArgumentException (message , lastError );
397+ } else {
398+ final String deprecationMessage ;
399+ if (lastError != null ) {
400+ deprecationMessage = String .format (Locale .ROOT , "%s, caused by [%s]" , message , lastError .getMessage ());
401+ } else {
402+ deprecationMessage = message ;
403+ }
404+ DEPRECATION_LOGGER .deprecatedAndMaybeLog ("invalid_dynamic_template" , deprecationMessage );
405+ }
406+ }
407+ }
408+
409+ private static boolean containsSnippet (Map <?, ?> map , String snippet ) {
410+ for (Map .Entry <?, ?> entry : map .entrySet ()) {
411+ String key = entry .getKey ().toString ();
412+ if (key .contains (snippet )) {
413+ return true ;
414+ }
415+
416+ Object value = entry .getValue ();
417+ if (value instanceof Map ) {
418+ if (containsSnippet ((Map <?, ?>) value , snippet )) {
419+ return true ;
420+ }
421+ } else if (value instanceof List ) {
422+ if (containsSnippet ((List <?>) value , snippet )) {
423+ return true ;
424+ }
425+ } else if (value instanceof String ) {
426+ String valueString = (String ) value ;
427+ if (valueString .contains (snippet )) {
428+ return true ;
429+ }
430+ }
431+ }
432+
433+ return false ;
434+ }
435+
436+ private static boolean containsSnippet (List <?> list , String snippet ) {
437+ for (Object value : list ) {
438+ if (value instanceof Map ) {
439+ if (containsSnippet ((Map <?, ?>) value , snippet )) {
440+ return true ;
441+ }
442+ } else if (value instanceof List ) {
443+ if (containsSnippet ((List <?>) value , snippet )) {
444+ return true ;
445+ }
446+ } else if (value instanceof String ) {
447+ String valueString = (String ) value ;
448+ if (valueString .contains (snippet )) {
449+ return true ;
450+ }
451+ }
452+ }
453+ return false ;
454+ }
336455}
0 commit comments