diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/xcontent/DynamicTemplate.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/xcontent/DynamicTemplate.java index 4969dce0412..f29c0d4b4f1 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/xcontent/DynamicTemplate.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/xcontent/DynamicTemplate.java @@ -20,6 +20,7 @@ package org.elasticsearch.index.mapper.xcontent; import org.elasticsearch.ElasticSearchIllegalArgumentException; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.collect.Maps; import org.elasticsearch.common.regex.Regex; import org.elasticsearch.index.mapper.MapperParsingException; @@ -48,24 +49,50 @@ public class DynamicTemplate { } public static DynamicTemplate parse(String name, Map conf) throws MapperParsingException { - if (!conf.containsKey("match")) { - throw new MapperParsingException("template must have match set"); + String match = null; + String pathMatch = null; + String unmatch = null; + String pathUnmatch = null; + Map mapping = null; + String matchMappingType = null; + String matchPattern = "simple"; + + for (Map.Entry entry : conf.entrySet()) { + String propName = Strings.toUnderscoreCase(entry.getKey()); + if ("match".equals(propName)) { + match = entry.getValue().toString(); + } else if ("path_match".equals(propName)) { + pathMatch = entry.getValue().toString(); + } else if ("unmatch".equals(propName)) { + unmatch = entry.getValue().toString(); + } else if ("path_unmatch".equals(propName)) { + pathUnmatch = entry.getValue().toString(); + } else if ("match_mapping_type".equals(propName)) { + matchMappingType = entry.getValue().toString(); + } else if ("match_pattern".equals(propName)) { + matchPattern = entry.getValue().toString(); + } else if ("mapping".equals(propName)) { + mapping = (Map) entry.getValue(); + } } - String match = conf.get("match").toString(); - String unmatch = conf.containsKey("unmatch") ? conf.get("unmatch").toString() : null; - String matchMappingType = conf.containsKey("match_mapping_type") ? conf.get("match_mapping_type").toString() : null; - if (!conf.containsKey("mapping")) { + + if (match == null && pathMatch == null) { + throw new MapperParsingException("template must have match or path_match set"); + } + if (mapping == null) { throw new MapperParsingException("template must have mapping set"); } - Map mapping = (Map) conf.get("mapping"); - String matchType = conf.containsKey("match_pattern") ? conf.get("match_pattern").toString() : "simple"; - return new DynamicTemplate(name, conf, match, unmatch, matchMappingType, MatchType.fromString(matchType), mapping); + return new DynamicTemplate(name, conf, pathMatch, pathUnmatch, match, unmatch, matchMappingType, MatchType.fromString(matchPattern), mapping); } private final String name; private final Map conf; + private final String pathMatch; + + private final String pathUnmatch; + private final String match; private final String unmatch; @@ -76,9 +103,11 @@ public class DynamicTemplate { private final Map mapping; - public DynamicTemplate(String name, Map conf, String match, String unmatch, String matchMappingType, MatchType matchType, Map mapping) { + public DynamicTemplate(String name, Map conf, String pathMatch, String pathUnmatch, String match, String unmatch, String matchMappingType, MatchType matchType, Map mapping) { this.name = name; this.conf = conf; + this.pathMatch = pathMatch; + this.pathUnmatch = pathUnmatch; this.match = match; this.unmatch = unmatch; this.matchType = matchType; @@ -94,11 +123,17 @@ public class DynamicTemplate { return this.conf; } - public boolean match(String name, String dynamicType) { - if (!patternMatch(match, name)) { + public boolean match(ContentPath path, String name, String dynamicType) { + if (pathMatch != null && !patternMatch(pathMatch, path.fullPathAsText(name))) { return false; } - if (patternMatch(unmatch, name)) { + if (match != null && !patternMatch(match, name)) { + return false; + } + if (pathUnmatch != null && patternMatch(pathUnmatch, path.fullPathAsText(name))) { + return false; + } + if (unmatch != null && patternMatch(unmatch, name)) { return false; } if (matchMappingType != null) { diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/xcontent/ObjectMapper.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/xcontent/ObjectMapper.java index c159c7ec2f5..860edfc7283 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/xcontent/ObjectMapper.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/xcontent/ObjectMapper.java @@ -154,7 +154,7 @@ public class ObjectMapper implements XContentMapper, IncludeInAllMapper { private void parseProperties(ObjectMapper.Builder objBuilder, Map propsNode, ParserContext parserContext) { for (Map.Entry entry : propsNode.entrySet()) { - String propName = entry.getKey(); + String propName = Strings.toUnderscoreCase(entry.getKey()); Map propNode = (Map) entry.getValue(); String type; @@ -320,13 +320,18 @@ public class ObjectMapper implements XContentMapper, IncludeInAllMapper { if (objectMapper != null) { objectMapper.parse(context); } else { - BuilderContext builderContext = new BuilderContext(context.path()); XContentMapper.Builder builder = context.root().findTemplateBuilder(context, currentFieldName, "object"); if (builder == null) { builder = XContentMapperBuilders.object(currentFieldName).enabled(true).dynamic(dynamic).pathType(pathType); } + // remove the current field name from path, since the object builder adds it as well... + context.path().remove(); + BuilderContext builderContext = new BuilderContext(context.path()); objectMapper = builder.build(builderContext); putMapper(objectMapper); + + // now re add it and parse... + context.path().add(currentFieldName); objectMapper.parse(context); context.addedMapper(); } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/xcontent/RootObjectMapper.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/xcontent/RootObjectMapper.java index e319f44b208..54df66578a5 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/xcontent/RootObjectMapper.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/xcontent/RootObjectMapper.java @@ -167,7 +167,7 @@ public class RootObjectMapper extends ObjectMapper { } public XContentMapper.Builder findTemplateBuilder(ParseContext context, String name, String dynamicType) { - DynamicTemplate dynamicTemplate = findTemplate(name, dynamicType); + DynamicTemplate dynamicTemplate = findTemplate(context.path(), name, dynamicType); if (dynamicTemplate == null) { return null; } @@ -175,9 +175,9 @@ public class RootObjectMapper extends ObjectMapper { return parserContext.typeParser(dynamicTemplate.mappingType(dynamicType)).parse(name, dynamicTemplate.mappingForName(name, dynamicType), parserContext); } - public DynamicTemplate findTemplate(String name, String dynamicType) { + public DynamicTemplate findTemplate(ContentPath path, String name, String dynamicType) { for (DynamicTemplate dynamicTemplate : dynamicTemplates) { - if (dynamicTemplate.match(name, dynamicType)) { + if (dynamicTemplate.match(path, name, dynamicType)) { return dynamicTemplate; } } diff --git a/modules/elasticsearch/src/test/java/org/elasticsearch/index/mapper/xcontent/dynamictemplate/pathmatch/PathMatchDynamicTempalteTests.java b/modules/elasticsearch/src/test/java/org/elasticsearch/index/mapper/xcontent/dynamictemplate/pathmatch/PathMatchDynamicTempalteTests.java new file mode 100644 index 00000000000..66ff501d7c7 --- /dev/null +++ b/modules/elasticsearch/src/test/java/org/elasticsearch/index/mapper/xcontent/dynamictemplate/pathmatch/PathMatchDynamicTempalteTests.java @@ -0,0 +1,69 @@ +/* + * Licensed to Elastic Search and Shay Banon under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Elastic Search licenses this + * file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.index.mapper.xcontent.dynamictemplate.pathmatch; + +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.elasticsearch.index.mapper.FieldMappers; +import org.elasticsearch.index.mapper.xcontent.MapperTests; +import org.elasticsearch.index.mapper.xcontent.XContentDocumentMapper; +import org.testng.annotations.Test; + +import static org.elasticsearch.common.io.Streams.*; +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; + +/** + * @author kimchy (shay.banon) + */ +public class PathMatchDynamicTempalteTests { + + @Test public void testSimple() throws Exception { + String mapping = copyToStringFromClasspath("/org/elasticsearch/index/mapper/xcontent/dynamictemplate/pathmatch/test-mapping.json"); + XContentDocumentMapper docMapper = MapperTests.newParser().parse(mapping); + byte[] json = copyToBytesFromClasspath("/org/elasticsearch/index/mapper/xcontent/dynamictemplate/pathmatch/test-data.json"); + Document doc = docMapper.parse(json).doc(); + + Field f = doc.getField("name"); + assertThat(f.name(), equalTo("name")); + assertThat(f.stringValue(), equalTo("top_level")); + assertThat(f.isStored(), equalTo(false)); + + FieldMappers fieldMappers = docMapper.mappers().fullName("name"); + assertThat(fieldMappers.mappers().size(), equalTo(1)); + assertThat(fieldMappers.mapper().stored(), equalTo(false)); + + f = doc.getField("obj1.name"); + assertThat(f.name(), equalTo("obj1.name")); + assertThat(f.isStored(), equalTo(true)); + + fieldMappers = docMapper.mappers().fullName("obj1.name"); + assertThat(fieldMappers.mappers().size(), equalTo(1)); + assertThat(fieldMappers.mapper().stored(), equalTo(true)); + + f = doc.getField("obj1.obj2.name"); + assertThat(f.name(), equalTo("obj1.obj2.name")); + assertThat(f.isStored(), equalTo(false)); + + fieldMappers = docMapper.mappers().fullName("obj1.obj2.name"); + assertThat(fieldMappers.mappers().size(), equalTo(1)); + assertThat(fieldMappers.mapper().stored(), equalTo(false)); + } +} diff --git a/modules/elasticsearch/src/test/java/org/elasticsearch/index/mapper/xcontent/dynamictemplate/pathmatch/test-data.json b/modules/elasticsearch/src/test/java/org/elasticsearch/index/mapper/xcontent/dynamictemplate/pathmatch/test-data.json new file mode 100644 index 00000000000..096992d519f --- /dev/null +++ b/modules/elasticsearch/src/test/java/org/elasticsearch/index/mapper/xcontent/dynamictemplate/pathmatch/test-data.json @@ -0,0 +1,10 @@ +{ + "_id" : "1", + "name" : "top_level", + "obj1" : { + "name" : "obj1_level", + "obj2" : { + "name" : "obj2_level" + } + } +} \ No newline at end of file diff --git a/modules/elasticsearch/src/test/java/org/elasticsearch/index/mapper/xcontent/dynamictemplate/pathmatch/test-mapping.json b/modules/elasticsearch/src/test/java/org/elasticsearch/index/mapper/xcontent/dynamictemplate/pathmatch/test-mapping.json new file mode 100644 index 00000000000..b791b421243 --- /dev/null +++ b/modules/elasticsearch/src/test/java/org/elasticsearch/index/mapper/xcontent/dynamictemplate/pathmatch/test-mapping.json @@ -0,0 +1,22 @@ +{ + "person" : { + "dynamic_templates" : [ + { + "template_1" : { + "path_match" : "obj1.obj2.*", + "mapping" : { + "store" : "no" + } + } + }, + { + "template_2" : { + "path_match" : "obj1.*", + "mapping" : { + "store" : "yes" + } + } + } + ] + } +} \ No newline at end of file