All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.index.mapper.xcontent.ObjectMapper Maven / Gradle / Ivy

There is a newer version: 8.15.1
Show newest version
/*
 * 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;

import org.elasticsearch.ElasticSearchIllegalStateException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
import org.elasticsearch.common.util.concurrent.ThreadSafe;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.mapper.*;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.elasticsearch.common.collect.ImmutableMap.*;
import static org.elasticsearch.common.collect.Lists.*;
import static org.elasticsearch.common.collect.MapBuilder.*;
import static org.elasticsearch.common.xcontent.support.XContentMapValues.*;
import static org.elasticsearch.index.mapper.xcontent.XContentMapperBuilders.*;
import static org.elasticsearch.index.mapper.xcontent.XContentTypeParsers.*;

/**
 * @author kimchy (shay.banon)
 */
@ThreadSafe
public class ObjectMapper implements XContentMapper, IncludeInAllMapper {

    public static final String CONTENT_TYPE = "object";

    public static class Defaults {
        public static final boolean ENABLED = true;
        public static final boolean DYNAMIC = true;
        public static final ContentPath.Type PATH_TYPE = ContentPath.Type.FULL;
    }

    public static class Builder extends XContentMapper.Builder {

        protected boolean enabled = Defaults.ENABLED;

        protected boolean dynamic = Defaults.DYNAMIC;

        protected ContentPath.Type pathType = Defaults.PATH_TYPE;

        protected Boolean includeInAll;

        protected final List mappersBuilders = newArrayList();

        public Builder(String name) {
            super(name);
            this.builder = (T) this;
        }

        public T enabled(boolean enabled) {
            this.enabled = enabled;
            return builder;
        }

        public T dynamic(boolean dynamic) {
            this.dynamic = dynamic;
            return builder;
        }

        public T pathType(ContentPath.Type pathType) {
            this.pathType = pathType;
            return builder;
        }

        public T includeInAll(boolean includeInAll) {
            this.includeInAll = includeInAll;
            return builder;
        }

        public T add(XContentMapper.Builder builder) {
            mappersBuilders.add(builder);
            return this.builder;
        }

        @Override public Y build(BuilderContext context) {
            ContentPath.Type origPathType = context.path().pathType();
            context.path().pathType(pathType);
            context.path().add(name);

            Map mappers = new HashMap();
            for (XContentMapper.Builder builder : mappersBuilders) {
                XContentMapper mapper = builder.build(context);
                mappers.put(mapper.name(), mapper);
            }
            ObjectMapper objectMapper = createMapper(name, enabled, dynamic, pathType, mappers);

            context.path().pathType(origPathType);
            context.path().remove();

            objectMapper.includeInAll(includeInAll);

            return (Y) objectMapper;
        }

        protected ObjectMapper createMapper(String name, boolean enabled, boolean dynamic, ContentPath.Type pathType, Map mappers) {
            return new ObjectMapper(name, enabled, dynamic, pathType, mappers);
        }
    }

    public static class TypeParser implements XContentMapper.TypeParser {
        @Override public XContentMapper.Builder parse(String name, Map node, ParserContext parserContext) throws MapperParsingException {
            Map objectNode = node;
            ObjectMapper.Builder builder = createBuilder(name);

            for (Map.Entry entry : objectNode.entrySet()) {
                String fieldName = Strings.toUnderscoreCase(entry.getKey());
                Object fieldNode = entry.getValue();

                if (fieldName.equals("dynamic")) {
                    builder.dynamic(nodeBooleanValue(fieldNode));
                } else if (fieldName.equals("type")) {
                    String type = fieldNode.toString();
                    if (!type.equals("object")) {
                        throw new MapperParsingException("Trying to parse an object but has a different type [" + type + "] for [" + name + "]");
                    }
                } else if (fieldName.equals("enabled")) {
                    builder.enabled(nodeBooleanValue(fieldNode));
                } else if (fieldName.equals("path")) {
                    builder.pathType(parsePathType(name, fieldNode.toString()));
                } else if (fieldName.equals("properties")) {
                    parseProperties(builder, (Map) fieldNode, parserContext);
                } else if (fieldName.equals("include_in_all")) {
                    builder.includeInAll(nodeBooleanValue(fieldNode));
                } else {
                    processField(builder, fieldName, fieldNode);
                }
            }
            return builder;
        }

        private void parseProperties(ObjectMapper.Builder objBuilder, Map propsNode, ParserContext parserContext) {
            for (Map.Entry entry : propsNode.entrySet()) {
                String propName = entry.getKey();
                Map propNode = (Map) entry.getValue();

                String type;
                Object typeNode = propNode.get("type");
                if (typeNode != null) {
                    type = typeNode.toString();
                } else {
                    // lets see if we can derive this...
                    if (propNode.get("properties") != null) {
                        type = ObjectMapper.CONTENT_TYPE;
                    } else if (propNode.get("fields") != null) {
                        type = MultiFieldMapper.CONTENT_TYPE;
                    } else {
                        throw new MapperParsingException("No type specified for property [" + propName + "]");
                    }
                }

                XContentMapper.TypeParser typeParser = parserContext.typeParser(type);
                if (typeParser == null) {
                    throw new MapperParsingException("No handler for type [" + type + "] declared on field [" + propName + "]");
                }
                objBuilder.add(typeParser.parse(propName, propNode, parserContext));
            }
        }

        protected Builder createBuilder(String name) {
            return object(name);
        }

        protected void processField(Builder builder, String fieldName, Object fieldNode) {

        }
    }

    private final String name;

    private final boolean enabled;

    private final boolean dynamic;

    private final ContentPath.Type pathType;

    private Boolean includeInAll;

    private volatile ImmutableMap mappers = ImmutableMap.of();

    private final Object mutex = new Object();

    protected ObjectMapper(String name) {
        this(name, Defaults.ENABLED, Defaults.DYNAMIC, Defaults.PATH_TYPE);
    }


    protected ObjectMapper(String name, boolean enabled, boolean dynamic, ContentPath.Type pathType) {
        this(name, enabled, dynamic, pathType, null);
    }

    ObjectMapper(String name, boolean enabled, boolean dynamic, ContentPath.Type pathType, Map mappers) {
        this.name = name;
        this.enabled = enabled;
        this.dynamic = dynamic;
        this.pathType = pathType;
        if (mappers != null) {
            this.mappers = copyOf(mappers);
        }
    }

    @Override public String name() {
        return this.name;
    }

    @Override public void includeInAll(Boolean includeInAll) {
        if (includeInAll == null) {
            return;
        }
        this.includeInAll = includeInAll;
        // when called from outside, apply this on all the inner mappers
        for (XContentMapper mapper : mappers.values()) {
            if (mapper instanceof IncludeInAllMapper) {
                ((IncludeInAllMapper) mapper).includeInAll(includeInAll);
            }
        }
    }

    public ObjectMapper putMapper(XContentMapper mapper) {
        if (mapper instanceof IncludeInAllMapper) {
            ((IncludeInAllMapper) mapper).includeInAll(includeInAll);
        }
        synchronized (mutex) {
            mappers = newMapBuilder(mappers).put(mapper.name(), mapper).immutableMap();
        }
        return this;
    }

    @Override public void traverse(FieldMapperListener fieldMapperListener) {
        for (XContentMapper mapper : mappers.values()) {
            mapper.traverse(fieldMapperListener);
        }
    }

    public void parse(ParseContext context) throws IOException {
        if (!enabled) {
            context.parser().skipChildren();
            return;
        }
        XContentParser parser = context.parser();

        ContentPath.Type origPathType = context.path().pathType();
        context.path().pathType(pathType);

        String currentFieldName = parser.currentName();
        XContentParser.Token token = parser.currentToken();
        if (token == XContentParser.Token.VALUE_NULL) {
            // the object is null ("obj1" : null), simply bail
            return;
        }
        // if we are at the end of the previous object, advance
        if (token == XContentParser.Token.END_OBJECT) {
            token = parser.nextToken();
        }
        if (token == XContentParser.Token.START_OBJECT) {
            // if we are just starting an OBJECT, advance, this is the object we are parsing, we need the name first
            token = parser.nextToken();
        }
        while (token != XContentParser.Token.END_OBJECT) {
            if (token == XContentParser.Token.START_OBJECT) {
                serializeObject(context, currentFieldName);
            } else if (token == XContentParser.Token.START_ARRAY) {
                serializeArray(context, currentFieldName);
            } else if (token == XContentParser.Token.FIELD_NAME) {
                currentFieldName = parser.currentName();
            } else if (token == XContentParser.Token.VALUE_NULL) {
                serializeNullValue(context, currentFieldName);
            } else if (token.isValue()) {
                serializeValue(context, currentFieldName, token);
            }
            token = parser.nextToken();
        }
        // restore the enable path flag
        context.path().pathType(origPathType);
    }

    private void serializeNullValue(ParseContext context, String lastFieldName) throws IOException {
        // we can only handle null values if we have mappings for them
        XContentMapper mapper = mappers.get(lastFieldName);
        if (mapper != null) {
            mapper.parse(context);
        }
    }

    private void serializeObject(ParseContext context, String currentFieldName) throws IOException {
        context.path().add(currentFieldName);

        XContentMapper objectMapper = mappers.get(currentFieldName);
        if (objectMapper != null) {
            objectMapper.parse(context);
        } else {
            if (dynamic) {
                // we sync here just so we won't add it twice. Its not the end of the world
                // to sync here since next operations will get it before
                synchronized (mutex) {
                    objectMapper = mappers.get(currentFieldName);
                    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);
                        }
                        objectMapper = builder.build(builderContext);
                        putMapper(objectMapper);
                        objectMapper.parse(context);
                        context.addedMapper();
                    }
                }
            } else {
                // not dynamic, read everything up to end object
                context.parser().skipChildren();
            }
        }

        context.path().remove();
    }

    private void serializeArray(ParseContext context, String lastFieldName) throws IOException {
        XContentMapper mapper = mappers.get(lastFieldName);
        if (mapper != null && mapper instanceof ArrayValueMapperParser) {
            mapper.parse(context);
        } else {
            XContentParser parser = context.parser();
            XContentParser.Token token;
            while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
                if (token == XContentParser.Token.START_OBJECT) {
                    serializeObject(context, lastFieldName);
                } else if (token == XContentParser.Token.START_ARRAY) {
                    serializeArray(context, lastFieldName);
                } else if (token == XContentParser.Token.FIELD_NAME) {
                    lastFieldName = parser.currentName();
                } else if (token == XContentParser.Token.VALUE_NULL) {
                    serializeNullValue(context, lastFieldName);
                } else {
                    serializeValue(context, lastFieldName, token);
                }
            }
        }
    }

    private void serializeValue(final ParseContext context, String currentFieldName, XContentParser.Token token) throws IOException {
        XContentMapper mapper = mappers.get(currentFieldName);
        if (mapper != null) {
            mapper.parse(context);
            return;
        }
        if (!dynamic) {
            return;
        }
        // we sync here since we don't want to add this field twice to the document mapper
        // its not the end of the world, since we add it to the mappers once we create it
        // so next time we won't even get here for this field
        synchronized (mutex) {
            mapper = mappers.get(currentFieldName);
            if (mapper != null) {
                mapper.parse(context);
                return;
            }

            BuilderContext builderContext = new BuilderContext(context.path());
            if (token == XContentParser.Token.VALUE_STRING) {
                String text = context.parser().text();
                // check if it fits one of the date formats
                boolean isDate = false;
                // a safe check since "1" gets parsed as well
                if (text.contains(":") || text.contains("-") || text.contains("/")) {
                    for (FormatDateTimeFormatter dateTimeFormatter : context.root().dateTimeFormatters()) {
                        try {
                            dateTimeFormatter.parser().parseMillis(text);
                            XContentMapper.Builder builder = context.root().findTemplateBuilder(context, currentFieldName, "date");
                            if (builder == null) {
                                builder = dateField(currentFieldName).dateTimeFormatter(dateTimeFormatter);
                            }
                            mapper = builder.build(builderContext);
                            isDate = true;
                            break;
                        } catch (Exception e) {
                            // failure to parse this, continue
                        }
                    }
                }
                if (!isDate) {
                    XContentMapper.Builder builder = context.root().findTemplateBuilder(context, currentFieldName, "string");
                    if (builder == null) {
                        builder = stringField(currentFieldName);
                    }
                    mapper = builder.build(builderContext);
                }
            } else if (token == XContentParser.Token.VALUE_NUMBER) {
                XContentParser.NumberType numberType = context.parser().numberType();
                if (numberType == XContentParser.NumberType.INT) {
                    if (context.parser().estimatedNumberType()) {
                        XContentMapper.Builder builder = context.root().findTemplateBuilder(context, currentFieldName, "long");
                        if (builder == null) {
                            builder = longField(currentFieldName);
                        }
                        mapper = builder.build(builderContext);
                    } else {
                        XContentMapper.Builder builder = context.root().findTemplateBuilder(context, currentFieldName, "integer");
                        if (builder == null) {
                            builder = integerField(currentFieldName);
                        }
                        mapper = builder.build(builderContext);
                    }
                } else if (numberType == XContentParser.NumberType.LONG) {
                    XContentMapper.Builder builder = context.root().findTemplateBuilder(context, currentFieldName, "long");
                    if (builder == null) {
                        builder = longField(currentFieldName);
                    }
                    mapper = builder.build(builderContext);
                } else if (numberType == XContentParser.NumberType.FLOAT) {
                    if (context.parser().estimatedNumberType()) {
                        XContentMapper.Builder builder = context.root().findTemplateBuilder(context, currentFieldName, "double");
                        if (builder == null) {
                            builder = doubleField(currentFieldName);
                        }
                        mapper = builder.build(builderContext);
                    } else {
                        XContentMapper.Builder builder = context.root().findTemplateBuilder(context, currentFieldName, "float");
                        if (builder == null) {
                            builder = floatField(currentFieldName);
                        }
                        mapper = builder.build(builderContext);
                    }
                } else if (numberType == XContentParser.NumberType.DOUBLE) {
                    XContentMapper.Builder builder = context.root().findTemplateBuilder(context, currentFieldName, "double");
                    if (builder == null) {
                        builder = doubleField(currentFieldName);
                    }
                    mapper = builder.build(builderContext);
                }
            } else if (token == XContentParser.Token.VALUE_BOOLEAN) {
                XContentMapper.Builder builder = context.root().findTemplateBuilder(context, currentFieldName, "boolean");
                if (builder == null) {
                    builder = booleanField(currentFieldName);
                }
                mapper = builder.build(builderContext);
            } else {
                XContentMapper.Builder builder = context.root().findTemplateBuilder(context, currentFieldName, null);
                if (builder != null) {
                    mapper = builder.build(builderContext);
                } else {
                    // TODO how do we identify dynamically that its a binary value?
                    throw new ElasticSearchIllegalStateException("Can't handle serializing a dynamic type with content token [" + token + "] and field name [" + currentFieldName + "]");
                }
            }
            putMapper(mapper);
            mapper.traverse(new FieldMapperListener() {
                @Override public void fieldMapper(FieldMapper fieldMapper) {
                    context.docMapper().addFieldMapper(fieldMapper);
                }
            });

            mapper.parse(context);
            context.addedMapper();
        }
    }

    @Override public void merge(XContentMapper mergeWith, MergeContext mergeContext) throws MergeMappingException {
        if (!(mergeWith instanceof ObjectMapper)) {
            mergeContext.addConflict("Can't merge a non object mapping [" + mergeWith.name() + "] with an object mapping [" + name() + "]");
            return;
        }
        ObjectMapper mergeWithObject = (ObjectMapper) mergeWith;

        doMerge(mergeWithObject, mergeContext);

        synchronized (mutex) {
            for (XContentMapper mergeWithMapper : mergeWithObject.mappers.values()) {
                XContentMapper mergeIntoMapper = mappers.get(mergeWithMapper.name());
                if (mergeIntoMapper == null) {
                    // no mapping, simply add it if not simulating
                    if (!mergeContext.mergeFlags().simulate()) {
                        putMapper(mergeWithMapper);
                        if (mergeWithMapper instanceof AbstractFieldMapper) {
                            mergeContext.docMapper().addFieldMapper((FieldMapper) mergeWithMapper);
                        }
                    }
                } else {
                    if ((mergeWithMapper instanceof MultiFieldMapper) && !(mergeIntoMapper instanceof MultiFieldMapper)) {
                        MultiFieldMapper mergeWithMultiField = (MultiFieldMapper) mergeWithMapper;
                        mergeWithMultiField.merge(mergeIntoMapper, mergeContext);
                        if (!mergeContext.mergeFlags().simulate()) {
                            putMapper(mergeWithMultiField);
                            // now, raise events for all mappers
                            for (XContentMapper mapper : mergeWithMultiField.mappers().values()) {
                                if (mapper instanceof AbstractFieldMapper) {
                                    mergeContext.docMapper().addFieldMapper((FieldMapper) mapper);
                                }
                            }
                        }
                    } else {
                        mergeIntoMapper.merge(mergeWithMapper, mergeContext);
                    }
                }
            }
        }
    }

    protected void doMerge(ObjectMapper mergeWith, MergeContext mergeContext) {

    }

    @Override public void toXContent(XContentBuilder builder, Params params) throws IOException {
        toXContent(builder, params, XContentMapper.EMPTY_ARRAY);
    }

    public void toXContent(XContentBuilder builder, Params params, XContentMapper... additionalMappers) throws IOException {
        builder.startObject(name);
        builder.field("type", CONTENT_TYPE);
        builder.field("dynamic", dynamic);
        builder.field("enabled", enabled);
        builder.field("path", pathType.name().toLowerCase());
        if (includeInAll != null) {
            builder.field("include_in_all", includeInAll);
        }

        doXContent(builder, params);

        // check internal mappers first (this is only relevant for root object)
        for (XContentMapper mapper : mappers.values()) {
            if (mapper instanceof InternalMapper) {
                mapper.toXContent(builder, params);
            }
        }
        if (additionalMappers != null) {
            for (XContentMapper mapper : additionalMappers) {
                mapper.toXContent(builder, params);
            }
        }

        if (!mappers.isEmpty()) {
            builder.startObject("properties");
            for (XContentMapper mapper : mappers.values()) {
                if (!(mapper instanceof InternalMapper)) {
                    mapper.toXContent(builder, params);
                }
            }
            builder.endObject();
        }
        builder.endObject();
    }

    protected void doXContent(XContentBuilder builder, Params params) throws IOException {

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy