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 []{
@@ -128,15 +136,15 @@ public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext
128136 String fieldName = entry .getKey ();
129137 Object fieldNode = entry .getValue ();
130138 if (parseObjectOrDocumentTypeProperties (fieldName , fieldNode , parserContext , builder )
131- || processField (builder , fieldName , fieldNode , parserContext . indexVersionCreated () )) {
139+ || processField (builder , fieldName , fieldNode , parserContext )) {
132140 iterator .remove ();
133141 }
134142 }
135143 return builder ;
136144 }
137145
138146 protected boolean processField (RootObjectMapper .Builder builder , String fieldName , Object fieldNode ,
139- Version indexVersionCreated ) {
147+ ParserContext parserContext ) {
140148 if (fieldName .equals ("date_formats" ) || fieldName .equals ("dynamic_date_formats" )) {
141149 if (fieldNode instanceof List ) {
142150 List <DateFormatter > formatters = new ArrayList <>();
@@ -159,7 +167,7 @@ protected boolean processField(RootObjectMapper.Builder builder, String fieldNam
159167 // "template_1" : {
160168 // "match" : "*_test",
161169 // "match_mapping_type" : "string",
162- // "mapping" : { "type" : "string ", "store" : "yes" }
170+ // "mapping" : { "type" : "keyword ", "store" : "yes" }
163171 // }
164172 // }
165173 // ]
@@ -176,8 +184,9 @@ protected boolean processField(RootObjectMapper.Builder builder, String fieldNam
176184 Map .Entry <String , Object > entry = tmpl .entrySet ().iterator ().next ();
177185 String templateName = entry .getKey ();
178186 Map <String , Object > templateParams = (Map <String , Object >) entry .getValue ();
179- DynamicTemplate template = DynamicTemplate .parse (templateName , templateParams , indexVersionCreated );
187+ DynamicTemplate template = DynamicTemplate .parse (templateName , templateParams , parserContext . indexVersionCreated () );
180188 if (template != null ) {
189+ validateDynamicTemplate (parserContext , template );
181190 templates .add (template );
182191 }
183192 }
@@ -326,4 +335,111 @@ protected void doXContent(XContentBuilder builder, ToXContent.Params params) thr
326335 builder .field ("numeric_detection" , numericDetection .value ());
327336 }
328337 }
338+
339+ private static void validateDynamicTemplate (Mapper .TypeParser .ParserContext parserContext ,
340+ DynamicTemplate dynamicTemplate ) {
341+
342+ if (containsSnippet (dynamicTemplate .getMapping (), "{name}" )) {
343+ // Can't validate template, because field names can't be guessed up front.
344+ return ;
345+ }
346+
347+ final XContentFieldType [] types ;
348+ if (dynamicTemplate .getXContentFieldType () != null ) {
349+ types = new XContentFieldType []{dynamicTemplate .getXContentFieldType ()};
350+ } else {
351+ types = XContentFieldType .values ();
352+ }
353+
354+ Exception lastError = null ;
355+ boolean dynamicTemplateInvalid = true ;
356+
357+ for (XContentFieldType contentFieldType : types ) {
358+ String defaultDynamicType = contentFieldType .defaultMappingType ();
359+ String mappingType = dynamicTemplate .mappingType (defaultDynamicType );
360+ Mapper .TypeParser typeParser = parserContext .typeParser (mappingType );
361+ if (typeParser == null ) {
362+ lastError = new IllegalArgumentException ("No mapper found for type [" + mappingType + "]" );
363+ continue ;
364+ }
365+
366+ Map <String , Object > fieldTypeConfig = dynamicTemplate .mappingForName ("__dummy__" , defaultDynamicType );
367+ fieldTypeConfig .remove ("type" );
368+ try {
369+ Mapper .Builder <?, ?> dummyBuilder = typeParser .parse ("__dummy__" , fieldTypeConfig , parserContext );
370+ if (fieldTypeConfig .isEmpty ()) {
371+ Settings indexSettings = parserContext .mapperService ().getIndexSettings ().getSettings ();
372+ BuilderContext builderContext = new BuilderContext (indexSettings , new ContentPath (1 ));
373+ dummyBuilder .build (builderContext );
374+ dynamicTemplateInvalid = false ;
375+ break ;
376+ } else {
377+ lastError = new IllegalArgumentException ("Unused mapping attributes [" + fieldTypeConfig + "]" );
378+ }
379+ } catch (Exception e ) {
380+ lastError = e ;
381+ }
382+ }
383+
384+ final boolean shouldEmitDeprecationWarning = parserContext .indexVersionCreated ().onOrAfter (Version .V_7_7_0 );
385+ if (dynamicTemplateInvalid && shouldEmitDeprecationWarning ) {
386+ String message = String .format (Locale .ROOT , "dynamic template [%s] has invalid content [%s]" ,
387+ dynamicTemplate .getName (), Strings .toString (dynamicTemplate ));
388+
389+ final String deprecationMessage ;
390+ if (lastError != null ) {
391+ deprecationMessage = String .format (Locale .ROOT , "%s, caused by [%s]" , message , lastError .getMessage ());
392+ } else {
393+ deprecationMessage = message ;
394+ }
395+ DEPRECATION_LOGGER .deprecatedAndMaybeLog ("invalid_dynamic_template" , deprecationMessage );
396+ }
397+ }
398+
399+ private static boolean containsSnippet (Map <?, ?> map , String snippet ) {
400+ for (Map .Entry <?, ?> entry : map .entrySet ()) {
401+ String key = entry .getKey ().toString ();
402+ if (key .contains (snippet )) {
403+ return true ;
404+ }
405+
406+ Object value = entry .getValue ();
407+ if (value instanceof Map ) {
408+ if (containsSnippet ((Map <?, ?>) value , snippet )) {
409+ return true ;
410+ }
411+ } else if (value instanceof List ) {
412+ if (containsSnippet ((List <?>) value , snippet )) {
413+ return true ;
414+ }
415+ } else if (value instanceof String ) {
416+ String valueString = (String ) value ;
417+ if (valueString .contains (snippet )) {
418+ return true ;
419+ }
420+ }
421+ }
422+
423+ return false ;
424+ }
425+
426+ private static boolean containsSnippet (List <?> list , String snippet ) {
427+ for (Object value : list ) {
428+ if (value instanceof Map ) {
429+ if (containsSnippet ((Map <?, ?>) value , snippet )) {
430+ return true ;
431+ }
432+ } else if (value instanceof List ) {
433+ if (containsSnippet ((List <?>) value , snippet )) {
434+ return true ;
435+ }
436+ } else if (value instanceof String ) {
437+ String valueString = (String ) value ;
438+ if (valueString .contains (snippet )) {
439+ return true ;
440+ }
441+ }
442+ }
443+ return false ;
444+ }
329445}
0 commit comments