Skip to content

Commit e7fd49e

Browse files
committed
Check if root mapping is actually valid
When a mapping is declared and the type is known from the uri then the type can be skipped in the body (see #4483). However, there was no check if the given keys actually make a valid mapping. closes #5864 closes #6093
1 parent f9d96c4 commit e7fd49e

14 files changed

+238
-75
lines changed

src/main/java/org/elasticsearch/index/mapper/DocumentMapperParser.java

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import com.google.common.collect.ImmutableMap;
2323
import com.google.common.collect.Maps;
24+
import org.elasticsearch.ElasticsearchParseException;
2425
import org.elasticsearch.Version;
2526
import org.elasticsearch.cluster.metadata.IndexMetaData;
2627
import org.elasticsearch.common.Nullable;
@@ -48,7 +49,9 @@
4849
import org.elasticsearch.index.settings.IndexSettings;
4950
import org.elasticsearch.index.similarity.SimilarityLookupService;
5051

52+
import java.util.Iterator;
5153
import java.util.Map;
54+
import java.util.Set;
5255

5356
import static org.elasticsearch.index.mapper.MapperBuilders.doc;
5457

@@ -201,31 +204,38 @@ private DocumentMapper parse(String type, Map<String, Object> mapping, String de
201204

202205

203206
Mapper.TypeParser.ParserContext parserContext = parserContext();
207+
// parse RootObjectMapper
204208
DocumentMapper.Builder docBuilder = doc(index.name(), indexSettings, (RootObjectMapper.Builder) rootObjectTypeParser.parse(type, mapping, parserContext));
205-
206-
for (Map.Entry<String, Object> entry : mapping.entrySet()) {
209+
Iterator<Map.Entry<String, Object>> iterator = mapping.entrySet().iterator();
210+
// parse DocumentMapper
211+
while(iterator.hasNext()) {
212+
Map.Entry<String, Object> entry = iterator.next();
207213
String fieldName = Strings.toUnderscoreCase(entry.getKey());
208214
Object fieldNode = entry.getValue();
209215

210216
if ("index_analyzer".equals(fieldName)) {
217+
iterator.remove();
211218
NamedAnalyzer analyzer = analysisService.analyzer(fieldNode.toString());
212219
if (analyzer == null) {
213220
throw new MapperParsingException("Analyzer [" + fieldNode.toString() + "] not found for index_analyzer setting on root type [" + type + "]");
214221
}
215222
docBuilder.indexAnalyzer(analyzer);
216223
} else if ("search_analyzer".equals(fieldName)) {
224+
iterator.remove();
217225
NamedAnalyzer analyzer = analysisService.analyzer(fieldNode.toString());
218226
if (analyzer == null) {
219227
throw new MapperParsingException("Analyzer [" + fieldNode.toString() + "] not found for search_analyzer setting on root type [" + type + "]");
220228
}
221229
docBuilder.searchAnalyzer(analyzer);
222230
} else if ("search_quote_analyzer".equals(fieldName)) {
231+
iterator.remove();
223232
NamedAnalyzer analyzer = analysisService.analyzer(fieldNode.toString());
224233
if (analyzer == null) {
225234
throw new MapperParsingException("Analyzer [" + fieldNode.toString() + "] not found for search_analyzer setting on root type [" + type + "]");
226235
}
227236
docBuilder.searchQuoteAnalyzer(analyzer);
228237
} else if ("analyzer".equals(fieldName)) {
238+
iterator.remove();
229239
NamedAnalyzer analyzer = analysisService.analyzer(fieldNode.toString());
230240
if (analyzer == null) {
231241
throw new MapperParsingException("Analyzer [" + fieldNode.toString() + "] not found for analyzer setting on root type [" + type + "]");
@@ -235,11 +245,25 @@ private DocumentMapper parse(String type, Map<String, Object> mapping, String de
235245
} else {
236246
Mapper.TypeParser typeParser = rootTypeParsers.get(fieldName);
237247
if (typeParser != null) {
248+
iterator.remove();
238249
docBuilder.put(typeParser.parse(fieldName, (Map<String, Object>) fieldNode, parserContext));
239250
}
240251
}
241252
}
242253

254+
ImmutableMap<String, Object> attributes = ImmutableMap.of();
255+
if (mapping.containsKey("_meta")) {
256+
attributes = ImmutableMap.copyOf((Map<String, Object>) mapping.remove("_meta"));
257+
}
258+
docBuilder.meta(attributes);
259+
260+
if (!mapping.isEmpty()) {
261+
StringBuilder remainingFields = new StringBuilder();
262+
for (String key : mapping.keySet()) {
263+
remainingFields.append(" [").append(key).append(" : ").append(mapping.get(key).toString()).append("]");
264+
}
265+
throw new MapperParsingException("Root type mapping not empty after parsing! Remaining fields:" + remainingFields.toString());
266+
}
243267
if (!docBuilder.hasIndexAnalyzer()) {
244268
docBuilder.indexAnalyzer(analysisService.defaultIndexAnalyzer());
245269
}
@@ -250,12 +274,6 @@ private DocumentMapper parse(String type, Map<String, Object> mapping, String de
250274
docBuilder.searchAnalyzer(analysisService.defaultSearchQuoteAnalyzer());
251275
}
252276

253-
ImmutableMap<String, Object> attributes = ImmutableMap.of();
254-
if (mapping.containsKey("_meta")) {
255-
attributes = ImmutableMap.copyOf((Map<String, Object>) mapping.get("_meta"));
256-
}
257-
docBuilder.meta(attributes);
258-
259277
DocumentMapper documentMapper = docBuilder.build(this);
260278
// update the source with the generated one
261279
documentMapper.refreshSource();
@@ -279,15 +297,13 @@ private Tuple<String, Map<String, Object>> extractMapping(String type, Map<Strin
279297
// if we don't have any keys throw an exception
280298
throw new MapperParsingException("malformed mapping no root object found");
281299
}
282-
283300
String rootName = root.keySet().iterator().next();
284301
Tuple<String, Map<String, Object>> mapping;
285302
if (type == null || type.equals(rootName)) {
286303
mapping = new Tuple<>(rootName, (Map<String, Object>) root.get(rootName));
287304
} else {
288305
mapping = new Tuple<>(type, root);
289306
}
290-
291307
return mapping;
292308
}
293309
}

src/main/java/org/elasticsearch/index/mapper/object/ObjectMapper.java

Lines changed: 60 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
package org.elasticsearch.index.mapper.object;
2121

2222
import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
23-
import org.apache.lucene.document.Field;
2423
import org.apache.lucene.document.XStringField;
2524
import org.apache.lucene.index.IndexableField;
2625
import org.apache.lucene.index.Term;
@@ -180,63 +179,80 @@ protected ObjectMapper createMapper(String name, String fullPath, boolean enable
180179
public static class TypeParser implements Mapper.TypeParser {
181180
@Override
182181
public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext parserContext) throws MapperParsingException {
183-
Map<String, Object> objectNode = node;
184182
ObjectMapper.Builder builder = createBuilder(name);
185-
186-
boolean nested = false;
187-
boolean nestedIncludeInParent = false;
188-
boolean nestedIncludeInRoot = false;
189-
for (Map.Entry<String, Object> entry : objectNode.entrySet()) {
183+
for (Map.Entry<String, Object> entry : node.entrySet()) {
190184
String fieldName = Strings.toUnderscoreCase(entry.getKey());
191185
Object fieldNode = entry.getValue();
186+
parseObjectOrDocumentTypeProperties(fieldName, fieldNode, parserContext, builder);
187+
parseObjectProperties(name, fieldName, fieldNode, builder);
188+
}
189+
parseNested(name, node, builder);
190+
return builder;
191+
}
192192

193-
if (fieldName.equals("dynamic")) {
194-
String value = fieldNode.toString();
195-
if (value.equalsIgnoreCase("strict")) {
196-
builder.dynamic(Dynamic.STRICT);
197-
} else {
198-
builder.dynamic(nodeBooleanValue(fieldNode) ? Dynamic.TRUE : Dynamic.FALSE);
199-
}
200-
} else if (fieldName.equals("type")) {
201-
String type = fieldNode.toString();
202-
if (type.equals(CONTENT_TYPE)) {
203-
builder.nested = Nested.NO;
204-
} else if (type.equals(NESTED_CONTENT_TYPE)) {
205-
nested = true;
206-
} else {
207-
throw new MapperParsingException("Trying to parse an object but has a different type [" + type + "] for [" + name + "]");
208-
}
209-
} else if (fieldName.equals("include_in_parent")) {
210-
nestedIncludeInParent = nodeBooleanValue(fieldNode);
211-
} else if (fieldName.equals("include_in_root")) {
212-
nestedIncludeInRoot = nodeBooleanValue(fieldNode);
213-
} else if (fieldName.equals("enabled")) {
214-
builder.enabled(nodeBooleanValue(fieldNode));
215-
} else if (fieldName.equals("path")) {
216-
builder.pathType(parsePathType(name, fieldNode.toString()));
217-
} else if (fieldName.equals("properties")) {
218-
if (fieldNode instanceof Collection && ((Collection) fieldNode).isEmpty()) {
219-
// nothing to do here, empty (to support "properties: []" case)
220-
} else if (!(fieldNode instanceof Map)) {
221-
throw new ElasticsearchParseException("properties must be a map type");
222-
} else {
223-
parseProperties(builder, (Map<String, Object>) fieldNode, parserContext);
224-
}
225-
} else if (fieldName.equals("include_in_all")) {
226-
builder.includeInAll(nodeBooleanValue(fieldNode));
193+
protected static boolean parseObjectOrDocumentTypeProperties(String fieldName, Object fieldNode, ParserContext parserContext, ObjectMapper.Builder builder) {
194+
if (fieldName.equals("dynamic")) {
195+
String value = fieldNode.toString();
196+
if (value.equalsIgnoreCase("strict")) {
197+
builder.dynamic(Dynamic.STRICT);
198+
} else {
199+
builder.dynamic(nodeBooleanValue(fieldNode) ? Dynamic.TRUE : Dynamic.FALSE);
200+
}
201+
return true;
202+
} else if (fieldName.equals("enabled")) {
203+
builder.enabled(nodeBooleanValue(fieldNode));
204+
return true;
205+
} else if (fieldName.equals("properties")) {
206+
if (fieldNode instanceof Collection && ((Collection) fieldNode).isEmpty()) {
207+
// nothing to do here, empty (to support "properties: []" case)
208+
} else if (!(fieldNode instanceof Map)) {
209+
throw new ElasticsearchParseException("properties must be a map type");
227210
} else {
228-
processField(builder, fieldName, fieldNode);
211+
parseProperties(builder, (Map<String, Object>) fieldNode, parserContext);
229212
}
213+
return true;
230214
}
215+
return false;
216+
}
231217

218+
protected static void parseObjectProperties(String name, String fieldName, Object fieldNode, ObjectMapper.Builder builder) {
219+
if (fieldName.equals("path")) {
220+
builder.pathType(parsePathType(name, fieldNode.toString()));
221+
} else if (fieldName.equals("include_in_all")) {
222+
builder.includeInAll(nodeBooleanValue(fieldNode));
223+
}
224+
}
225+
226+
protected static void parseNested(String name, Map<String, Object> node, ObjectMapper.Builder builder) {
227+
boolean nested = false;
228+
boolean nestedIncludeInParent = false;
229+
boolean nestedIncludeInRoot = false;
230+
Object fieldNode = node.get("type");
231+
if (fieldNode!=null) {
232+
String type = fieldNode.toString();
233+
if (type.equals(CONTENT_TYPE)) {
234+
builder.nested = Nested.NO;
235+
} else if (type.equals(NESTED_CONTENT_TYPE)) {
236+
nested = true;
237+
} else {
238+
throw new MapperParsingException("Trying to parse an object but has a different type [" + type + "] for [" + name + "]");
239+
}
240+
}
241+
fieldNode = node.get("include_in_parent");
242+
if (fieldNode != null) {
243+
nestedIncludeInParent = nodeBooleanValue(fieldNode);
244+
}
245+
fieldNode = node.get("include_in_root");
246+
if (fieldNode != null) {
247+
nestedIncludeInRoot = nodeBooleanValue(fieldNode);
248+
}
232249
if (nested) {
233250
builder.nested = Nested.newNested(nestedIncludeInParent, nestedIncludeInRoot);
234251
}
235252

236-
return builder;
237253
}
238254

239-
private void parseProperties(ObjectMapper.Builder objBuilder, Map<String, Object> propsNode, ParserContext parserContext) {
255+
protected static void parseProperties(ObjectMapper.Builder objBuilder, Map<String, Object> propsNode, ParserContext parserContext) {
240256
for (Map.Entry<String, Object> entry : propsNode.entrySet()) {
241257
String propName = entry.getKey();
242258
Map<String, Object> propNode = (Map<String, Object>) entry.getValue();
@@ -270,10 +286,6 @@ private void parseProperties(ObjectMapper.Builder objBuilder, Map<String, Object
270286
protected Builder createBuilder(String name) {
271287
return object(name);
272288
}
273-
274-
protected void processField(Builder builder, String fieldName, Object fieldNode) {
275-
276-
}
277289
}
278290

279291
private final String name;

src/main/java/org/elasticsearch/index/mapper/object/RootObjectMapper.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import com.google.common.collect.Lists;
2323
import com.google.common.collect.Sets;
24+
import org.elasticsearch.common.Strings;
2425
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
2526
import org.elasticsearch.common.joda.Joda;
2627
import org.elasticsearch.common.xcontent.ToXContent;
@@ -29,10 +30,7 @@
2930
import org.elasticsearch.index.mapper.core.DateFieldMapper;
3031

3132
import java.io.IOException;
32-
import java.util.Arrays;
33-
import java.util.List;
34-
import java.util.Map;
35-
import java.util.Set;
33+
import java.util.*;
3634

3735
import static com.google.common.collect.Lists.newArrayList;
3836
import static org.elasticsearch.common.xcontent.support.XContentMapValues.nodeBooleanValue;
@@ -124,7 +122,23 @@ protected ObjectMapper.Builder createBuilder(String name) {
124122
}
125123

126124
@Override
127-
protected void processField(ObjectMapper.Builder builder, String fieldName, Object fieldNode) {
125+
public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext parserContext) throws MapperParsingException {
126+
127+
ObjectMapper.Builder builder = createBuilder(name);
128+
Iterator<Map.Entry<String, Object>> iterator = node.entrySet().iterator();
129+
while (iterator.hasNext()) {
130+
Map.Entry<String, Object> entry = iterator.next();
131+
String fieldName = Strings.toUnderscoreCase(entry.getKey());
132+
Object fieldNode = entry.getValue();
133+
if (parseObjectOrDocumentTypeProperties(fieldName, fieldNode, parserContext, builder)
134+
|| processField(builder, fieldName, fieldNode)) {
135+
iterator.remove();
136+
}
137+
}
138+
return builder;
139+
}
140+
141+
protected boolean processField(ObjectMapper.Builder builder, String fieldName, Object fieldNode) {
128142
if (fieldName.equals("date_formats") || fieldName.equals("dynamic_date_formats")) {
129143
List<FormatDateTimeFormatter> dateTimeFormatters = newArrayList();
130144
if (fieldNode instanceof List) {
@@ -141,6 +155,7 @@ protected void processField(ObjectMapper.Builder builder, String fieldName, Obje
141155
} else {
142156
((Builder) builder).dynamicDateTimeFormatter(dateTimeFormatters);
143157
}
158+
return true;
144159
} else if (fieldName.equals("dynamic_templates")) {
145160
// "dynamic_templates" : [
146161
// {
@@ -160,11 +175,15 @@ protected void processField(ObjectMapper.Builder builder, String fieldName, Obje
160175
Map.Entry<String, Object> entry = tmpl.entrySet().iterator().next();
161176
((Builder) builder).add(DynamicTemplate.parse(entry.getKey(), (Map<String, Object>) entry.getValue()));
162177
}
178+
return true;
163179
} else if (fieldName.equals("date_detection")) {
164180
((Builder) builder).dateDetection = nodeBooleanValue(fieldNode);
181+
return true;
165182
} else if (fieldName.equals("numeric_detection")) {
166183
((Builder) builder).numericDetection = nodeBooleanValue(fieldNode);
184+
return true;
167185
}
186+
return false;
168187
}
169188
}
170189

0 commit comments

Comments
 (0)