19
19
20
20
package org .elasticsearch .index .mapper ;
21
21
22
+ import org .apache .logging .log4j .LogManager ;
23
+ import org .apache .logging .log4j .Logger ;
22
24
import org .elasticsearch .Version ;
23
25
import org .elasticsearch .common .Explicit ;
24
26
import org .elasticsearch .common .Nullable ;
27
+ import org .elasticsearch .common .Strings ;
28
+ import org .elasticsearch .common .logging .DeprecationLogger ;
25
29
import org .elasticsearch .common .settings .Settings ;
26
30
import org .elasticsearch .common .time .DateFormatter ;
27
31
import org .elasticsearch .common .xcontent .ToXContent ;
34
38
import java .util .Collections ;
35
39
import java .util .Iterator ;
36
40
import java .util .List ;
41
+ import java .util .Locale ;
37
42
import java .util .Map ;
38
43
39
44
import static org .elasticsearch .common .xcontent .support .XContentMapValues .nodeBooleanValue ;
40
45
import static org .elasticsearch .index .mapper .TypeParsers .parseDateTimeFormatter ;
41
46
42
47
public class RootObjectMapper extends ObjectMapper {
43
48
49
+ private static final Logger LOGGER = LogManager .getLogger (RootObjectMapper .class );
50
+ private static final DeprecationLogger DEPRECATION_LOGGER = new DeprecationLogger (LOGGER );
51
+
44
52
public static class Defaults {
45
53
public static final DateFormatter [] DYNAMIC_DATE_TIME_FORMATTERS =
46
54
new DateFormatter []{
@@ -128,15 +136,15 @@ public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext
128
136
String fieldName = entry .getKey ();
129
137
Object fieldNode = entry .getValue ();
130
138
if (parseObjectOrDocumentTypeProperties (fieldName , fieldNode , parserContext , builder )
131
- || processField (builder , fieldName , fieldNode , parserContext . indexVersionCreated () )) {
139
+ || processField (builder , fieldName , fieldNode , parserContext )) {
132
140
iterator .remove ();
133
141
}
134
142
}
135
143
return builder ;
136
144
}
137
145
138
146
protected boolean processField (RootObjectMapper .Builder builder , String fieldName , Object fieldNode ,
139
- Version indexVersionCreated ) {
147
+ ParserContext parserContext ) {
140
148
if (fieldName .equals ("date_formats" ) || fieldName .equals ("dynamic_date_formats" )) {
141
149
if (fieldNode instanceof List ) {
142
150
List <DateFormatter > formatters = new ArrayList <>();
@@ -159,7 +167,7 @@ protected boolean processField(RootObjectMapper.Builder builder, String fieldNam
159
167
// "template_1" : {
160
168
// "match" : "*_test",
161
169
// "match_mapping_type" : "string",
162
- // "mapping" : { "type" : "string ", "store" : "yes" }
170
+ // "mapping" : { "type" : "keyword ", "store" : "yes" }
163
171
// }
164
172
// }
165
173
// ]
@@ -176,8 +184,9 @@ protected boolean processField(RootObjectMapper.Builder builder, String fieldNam
176
184
Map .Entry <String , Object > entry = tmpl .entrySet ().iterator ().next ();
177
185
String templateName = entry .getKey ();
178
186
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 () );
180
188
if (template != null ) {
189
+ validateDynamicTemplate (parserContext , template );
181
190
templates .add (template );
182
191
}
183
192
}
@@ -326,4 +335,111 @@ protected void doXContent(XContentBuilder builder, ToXContent.Params params) thr
326
335
builder .field ("numeric_detection" , numericDetection .value ());
327
336
}
328
337
}
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
+ }
329
445
}
0 commit comments