/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch 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.object;
import static org.elasticsearch.common.xcontent.support.XContentMapValues.nodeBooleanValue;
import static org.elasticsearch.common.xcontent.support.XContentMapValues.nodeIntegerValue;
import static org.elasticsearch.index.mapper.MapperBuilders.object;
import static org.elasticsearch.index.mapper.core.TypeParsers.parsePathType;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.Version;
import org.elasticsearch.common.Booleans;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.CopyOnWriteHashMap;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.mapper.ContentPath;
import org.elasticsearch.index.mapper.DocumentMapperParser;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.Mapper;
import org.elasticsearch.index.mapper.MapperParsingException;
import org.elasticsearch.index.mapper.MetadataFieldMapper;
import org.elasticsearch.index.mapper.core.TypeParsers;
import org.elasticsearch.index.mapper.internal.AllFieldMapper;
import org.elasticsearch.index.mapper.internal.TypeFieldMapper;
import com.google.common.collect.Iterables;
public class ObjectMapper extends Mapper implements AllFieldMapper.IncludeInAll, Cloneable {
public static final String CONTENT_TYPE = "object";
public static final String NESTED_CONTENT_TYPE = "nested";
private static final boolean DOTS_IN_FIELD_ALLOWED = Booleans.parseBooleanExact(
System.getProperty("mapper.allow_dots_in_name", "false"));
public static class Defaults {
public static final boolean ENABLED = true;
public static final Nested NESTED = Nested.NO;
public static final Dynamic DYNAMIC = null; // not set, inherited from root
public static final ContentPath.Type PATH_TYPE = ContentPath.Type.FULL;
public static final CqlCollection CQL_COLLECTION = CqlCollection.LIST;
public static final CqlStruct CQL_STRUCT = CqlStruct.UDT;
public static final boolean CQL_MANDATORY = true; // if true, force a read if field is missing when indexing.
public static final boolean CQL_PARTITION_KEY = false;
public static final boolean CQL_STATIC_COLUMN = false;
public static final int CQL_PRIMARY_KEY_ORDER = -1;
}
public static enum Dynamic {
TRUE,
FALSE,
STRICT
}
public static class Nested {
public static final Nested NO = new Nested(false, false, false);
public static Nested newNested(boolean includeInParent, boolean includeInRoot) {
return new Nested(true, includeInParent, includeInRoot);
}
private final boolean nested;
private final boolean includeInParent;
private final boolean includeInRoot;
private Nested(boolean nested, boolean includeInParent, boolean includeInRoot) {
this.nested = nested;
this.includeInParent = includeInParent;
this.includeInRoot = includeInRoot;
}
public boolean isNested() {
return nested;
}
public boolean isIncludeInParent() {
return includeInParent;
}
public boolean isIncludeInRoot() {
return includeInRoot;
}
}
public static class Builder<T extends Builder, Y extends ObjectMapper> extends Mapper.Builder<T, Y> {
protected boolean enabled = Defaults.ENABLED;
protected CqlCollection cqlCollection = Defaults.CQL_COLLECTION;
protected CqlStruct cqlStruct = Defaults.CQL_STRUCT;
protected String cqlUdtName = null;
protected boolean cqlMandatory = Defaults.CQL_MANDATORY;
protected boolean cqlPartitionKey = Defaults.CQL_PARTITION_KEY;
protected boolean cqlStaticColumn = Defaults.CQL_STATIC_COLUMN;
protected int cqlPrimaryKeyOrder = Defaults.CQL_PRIMARY_KEY_ORDER;
protected Nested nested = Defaults.NESTED;
protected Dynamic dynamic = Defaults.DYNAMIC;
protected ContentPath.Type pathType = Defaults.PATH_TYPE;
protected Boolean includeInAll;
protected final List<Mapper.Builder> mappersBuilders = new ArrayList<>();
public Builder(String name) {
super(name);
this.builder = (T) this;
}
public T enabled(boolean enabled) {
this.enabled = enabled;
return builder;
}
public T cqlCollection(CqlCollection cqlCollection) {
this.cqlCollection = cqlCollection;
return builder;
}
public T cqlStruct(CqlStruct cqlStruct) {
this.cqlStruct = cqlStruct;
return builder;
}
public T cqlUdtName(String cqlUdtName) {
this.cqlUdtName = cqlUdtName;
return builder;
}
public T cqlPartialUpdate(boolean cqlPartialUpdate) {
this.cqlMandatory = cqlPartialUpdate;
return builder;
}
public T cqlStaticColumn(boolean cqlStaticColumn) {
this.cqlStaticColumn = cqlStaticColumn;
return builder;
}
public T cqlPartitionKey(boolean cqlPartitionKey) {
this.cqlPartitionKey = cqlPartitionKey;
return builder;
}
public T cqlPrimaryKeyOrder(int cqlPrimaryKeyOrder) {
this.cqlPrimaryKeyOrder = cqlPrimaryKeyOrder;
return builder;
}
public T dynamic(Dynamic dynamic) {
this.dynamic = dynamic;
return builder;
}
public T nested(Nested nested) {
this.nested = nested;
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(Mapper.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<String, Mapper> mappers = new HashMap<>();
for (Mapper.Builder builder : mappersBuilders) {
Mapper mapper = builder.build(context);
mappers.put(mapper.simpleName(), mapper);
}
context.path().pathType(origPathType);
context.path().remove();
ObjectMapper objectMapper = createMapper(name, context.path().fullPathAsText(name), cqlCollection, cqlStruct, cqlUdtName, cqlMandatory, cqlPartitionKey, cqlPrimaryKeyOrder, cqlStaticColumn, enabled, nested, dynamic, pathType, mappers, context.indexSettings());
objectMapper = objectMapper.includeInAllIfNotSet(includeInAll);
return (Y) objectMapper;
}
protected ObjectMapper createMapper(String name, String fullPath, CqlCollection cqlCollection, CqlStruct cqlStruct, String cqlUdtName, boolean cqlPartialUpdate, boolean cqlPartitionKey, int cqlPrimaryKeyOrder, boolean cqlStaticColumn, boolean enabled, Nested nested, Dynamic dynamic, ContentPath.Type pathType, Map<String, Mapper> mappers, @Nullable Settings settings) {
return new ObjectMapper(name, fullPath, cqlCollection, cqlStruct, cqlUdtName, cqlPartialUpdate, cqlPartitionKey, cqlPrimaryKeyOrder, cqlStaticColumn, enabled, nested, dynamic, pathType, mappers);
}
}
public static class TypeParser implements Mapper.TypeParser {
@Override
public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext parserContext) throws MapperParsingException {
ObjectMapper.Builder builder = createBuilder(name);
parseNested(name, node, builder);
for (Iterator<Map.Entry<String, Object>> iterator = node.entrySet().iterator(); iterator.hasNext();) {
Map.Entry<String, Object> entry = iterator.next();
String fieldName = Strings.toUnderscoreCase(entry.getKey());
Object fieldNode = entry.getValue();
if (parseObjectOrDocumentTypeProperties(fieldName, fieldNode, parserContext, builder) || parseObjectProperties(name, fieldName, fieldNode, parserContext, builder)) {
iterator.remove();
}
}
return builder;
}
protected static boolean parseObjectOrDocumentTypeProperties(String fieldName, Object fieldNode, ParserContext parserContext, ObjectMapper.Builder builder) {
if (fieldName.equals("dynamic")) {
String value = fieldNode.toString();
if (value.equalsIgnoreCase("strict")) {
builder.dynamic(Dynamic.STRICT);
} else {
builder.dynamic(nodeBooleanValue(fieldNode) ? Dynamic.TRUE : Dynamic.FALSE);
}
return true;
} else if (fieldName.equals("enabled")) {
builder.enabled(nodeBooleanValue(fieldNode));
return true;
} else if (fieldName.equals(TypeParsers.CQL_MANDATORY)) {
builder.cqlPartialUpdate(nodeBooleanValue(fieldNode));
return true;
} else if (fieldName.equals(TypeParsers.CQL_PARTITION_KEY)) {
builder.cqlPartitionKey(nodeBooleanValue(fieldNode));
return true;
} else if (fieldName.equals(TypeParsers.CQL_STATIC_COLUMN)) {
builder.cqlStaticColumn(nodeBooleanValue(fieldNode));
return true;
} else if (fieldName.equals(TypeParsers.CQL_PRIMARY_KEY_ORDER)) {
builder.cqlPrimaryKeyOrder(nodeIntegerValue(fieldNode));
return true;
} else if (fieldName.equals(TypeParsers.CQL_COLLECTION)) {
String value = StringUtils.lowerCase(fieldNode.toString());
switch (value) {
case "list": builder.cqlCollection(CqlCollection.LIST); break;
case "set": builder.cqlCollection(CqlCollection.SET); break;
case "singleton": builder.cqlCollection(CqlCollection.SINGLETON); break;
}
return true;
} else if (fieldName.equals(TypeParsers.CQL_STRUCT)) {
String value = StringUtils.lowerCase(fieldNode.toString());
switch (value) {
case "tuple": builder.cqlStruct(CqlStruct.TUPLE); break;
case "map": builder.cqlStruct(CqlStruct.MAP); break;
case "udt": builder.cqlStruct(CqlStruct.UDT); break;
}
return true;
} else if (fieldName.equals(TypeParsers.CQL_UDT_NAME)) {
builder.cqlUdtName(fieldNode.toString());
return true;
} else if (fieldName.equals("properties")) {
if (fieldNode instanceof Collection && ((Collection) fieldNode).isEmpty()) {
// nothing to do here, empty (to support "properties: []" case)
} else if (!(fieldNode instanceof Map)) {
throw new ElasticsearchParseException("properties must be a map type");
} else {
parseProperties(builder, (Map<String, Object>) fieldNode, parserContext);
}
return true;
} else if (fieldName.equals("include_in_all")) {
builder.includeInAll(nodeBooleanValue(fieldNode));
return true;
}
return false;
}
protected static boolean parseObjectProperties(String name, String fieldName, Object fieldNode, ParserContext parserContext, ObjectMapper.Builder builder) {
if (fieldName.equals("path") && parserContext.indexVersionCreated().before(Version.V_2_0_0_beta1)) {
builder.pathType(parsePathType(name, fieldNode.toString()));
return true;
}
return false;
}
protected static void parseNested(String name, Map<String, Object> node, ObjectMapper.Builder builder) {
boolean nested = false;
boolean nestedIncludeInParent = false;
boolean nestedIncludeInRoot = false;
Object fieldNode = node.get("type");
if (fieldNode!=null) {
String type = fieldNode.toString();
if (type.equals(CONTENT_TYPE)) {
builder.nested = Nested.NO;
} else if (type.equals(NESTED_CONTENT_TYPE)) {
nested = true;
} else {
throw new MapperParsingException("Trying to parse an object but has a different type [" + type + "] for [" + name + "]");
}
}
fieldNode = node.get("include_in_parent");
if (fieldNode != null) {
nestedIncludeInParent = nodeBooleanValue(fieldNode);
node.remove("include_in_parent");
}
fieldNode = node.get("include_in_root");
if (fieldNode != null) {
nestedIncludeInRoot = nodeBooleanValue(fieldNode);
node.remove("include_in_root");
}
if (nested) {
builder.nested = Nested.newNested(nestedIncludeInParent, nestedIncludeInRoot);
}
}
protected static void parseProperties(ObjectMapper.Builder objBuilder, Map<String, Object> propsNode, ParserContext parserContext) {
Iterator<Map.Entry<String, Object>> iterator = propsNode.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Object> entry = iterator.next();
String fieldName = entry.getKey();
if (fieldName.contains(".") && DOTS_IN_FIELD_ALLOWED == false) {
throw new MapperParsingException("Field name [" + fieldName + "] cannot contain '.'");
}
// Should accept empty arrays, as a work around for when the
// user can't provide an empty Map. (PHP for example)
boolean isEmptyList = entry.getValue() instanceof List && ((List<?>) entry.getValue()).isEmpty();
if (entry.getValue() instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> propNode = (Map<String, Object>) 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.size() == 1 && propNode.get("enabled") != null) {
// if there is a single property with the enabled
// flag on it, make it an object
// (usually, setting enabled to false to not index
// any type, including core values, which
type = ObjectMapper.CONTENT_TYPE;
} else {
throw new MapperParsingException("No type specified for field [" + fieldName + "]");
}
}
Mapper.TypeParser typeParser = parserContext.typeParser(type);
if (typeParser == null) {
throw new MapperParsingException("No handler for type [" + type + "] declared on field [" + fieldName + "]");
}
objBuilder.add(typeParser.parse(fieldName, propNode, parserContext));
propNode.remove("type");
DocumentMapperParser.checkNoRemainingFields(fieldName, propNode, parserContext.indexVersionCreated());
iterator.remove();
} else if (isEmptyList) {
iterator.remove();
} else {
throw new MapperParsingException("Expected map for property [fields] on field [" + fieldName + "] but got a "
+ fieldName.getClass());
}
}
DocumentMapperParser.checkNoRemainingFields(propsNode, parserContext.indexVersionCreated(),
"DocType mapping definition has unsupported parameters: ");
}
protected Builder createBuilder(String name) {
return object(name);
}
}
private final String fullPath;
private final boolean enabled;
private final Nested nested;
private final CqlCollection cqlCollection;
private final CqlStruct cqlStruct;
private final String cqlUdtName;
private final boolean cqlPartialUpdate;
private final boolean cqlPartitionKey;
private final boolean cqlStaticColumn;
private final int cqlPrimaryKeyOrder;
private final String nestedTypePathAsString;
private final BytesRef nestedTypePathAsBytes;
private final Query nestedTypeFilter;
private volatile Dynamic dynamic;
private final ContentPath.Type pathType;
private Boolean includeInAll;
private volatile CopyOnWriteHashMap<String, Mapper> mappers;
ObjectMapper(String name, String fullPath, CqlCollection cqlCollection, CqlStruct cqlStruct, String cqlUdtName, boolean cqlPartialUpdate, boolean cqlPartitionKey, int cqlPrimaryKeyOrder, boolean cqlStaticColumn, boolean enabled, Nested nested, Dynamic dynamic, ContentPath.Type pathType, Map<String, Mapper> mappers) {
super(name);
this.fullPath = fullPath;
this.cqlCollection = cqlCollection;
this.cqlStruct = cqlStruct;
this.cqlUdtName = cqlUdtName;
this.cqlPartialUpdate = cqlPartialUpdate;
this.cqlPartitionKey = cqlPartitionKey;
this.cqlStaticColumn = cqlStaticColumn;
this.cqlPrimaryKeyOrder = cqlPrimaryKeyOrder;
this.enabled = enabled;
this.nested = nested;
this.dynamic = dynamic;
this.pathType = pathType;
if (mappers == null) {
this.mappers = new CopyOnWriteHashMap<>();
} else {
this.mappers = CopyOnWriteHashMap.copyOf(mappers);
}
this.nestedTypePathAsString = "__" + fullPath;
this.nestedTypePathAsBytes = new BytesRef(nestedTypePathAsString);
this.nestedTypeFilter = new TermQuery(new Term(TypeFieldMapper.NAME, nestedTypePathAsBytes));
}
@Override
protected ObjectMapper clone() {
ObjectMapper clone;
try {
clone = (ObjectMapper) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException();
}
return clone;
}
/**
* Build a mapping update with the provided sub mapping update.
*/
public ObjectMapper mappingUpdate(Mapper mapper) {
ObjectMapper mappingUpdate = clone();
// reset the sub mappers
mappingUpdate.mappers = new CopyOnWriteHashMap<>();
mappingUpdate.putMapper(mapper);
return mappingUpdate;
}
@Override
public String name() {
return this.fullPath;
}
public boolean isEnabled() {
return this.enabled;
}
public ContentPath.Type pathType() {
return pathType;
}
public Mapper getMapper(String field) {
return mappers.get(field);
}
// pkg-private for testing
Boolean includeInAll() {
return includeInAll;
}
@Override
public ObjectMapper includeInAll(Boolean includeInAll) {
if (includeInAll == null) {
return this;
}
ObjectMapper clone = clone();
clone.includeInAll = includeInAll;
// when called from outside, apply this on all the inner mappers
for (Mapper mapper : clone.mappers.values()) {
if (mapper instanceof AllFieldMapper.IncludeInAll) {
clone.putMapper(((AllFieldMapper.IncludeInAll) mapper).includeInAll(includeInAll));
}
}
return clone;
}
@Override
public ObjectMapper includeInAllIfNotSet(Boolean includeInAll) {
if (includeInAll == null || this.includeInAll != null) {
return this;
}
ObjectMapper clone = clone();
clone.includeInAll = includeInAll;
// when called from outside, apply this on all the inner mappers
for (Mapper mapper : clone.mappers.values()) {
if (mapper instanceof AllFieldMapper.IncludeInAll) {
clone.putMapper(((AllFieldMapper.IncludeInAll) mapper).includeInAllIfNotSet(includeInAll));
}
}
return clone;
}
@Override
public ObjectMapper unsetIncludeInAll() {
if (includeInAll == null) {
return this;
}
ObjectMapper clone = clone();
clone.includeInAll = null;
// when called from outside, apply this on all the inner mappers
for (Mapper mapper : mappers.values()) {
if (mapper instanceof AllFieldMapper.IncludeInAll) {
clone.putMapper(((AllFieldMapper.IncludeInAll) mapper).unsetIncludeInAll());
}
}
return clone;
}
public Nested nested() {
return this.nested;
}
public Query nestedTypeFilter() {
return this.nestedTypeFilter;
}
protected void putMapper(Mapper mapper) {
if (mapper instanceof AllFieldMapper.IncludeInAll) {
mapper = ((AllFieldMapper.IncludeInAll) mapper).includeInAllIfNotSet(includeInAll);
}
mappers = mappers.copyAndPut(mapper.simpleName(), mapper);
}
@Override
public Iterator<Mapper> iterator() {
return mappers.values().iterator();
}
public String fullPath() {
return this.fullPath;
}
public String nestedTypePathAsString() {
return nestedTypePathAsString;
}
public final Dynamic dynamic() {
return dynamic;
}
@Override
public ObjectMapper merge(Mapper mergeWith, boolean updateAllTypes) {
if (!(mergeWith instanceof ObjectMapper)) {
throw new IllegalArgumentException("Can't merge a non object mapping [" + mergeWith.name() + "] with an object mapping [" + name() + "]");
}
ObjectMapper mergeWithObject = (ObjectMapper) mergeWith;
ObjectMapper merged = clone();
merged = merged.includeInAllIfNotSet(mergeWithObject.includeInAll);
merged.doMerge(mergeWithObject, updateAllTypes);
return merged;
}
protected void doMerge(final ObjectMapper mergeWith, boolean updateAllTypes) {
if (nested().isNested()) {
if (!mergeWith.nested().isNested()) {
throw new IllegalArgumentException("object mapping [" + name() + "] can't be changed from nested to non-nested");
}
} else {
if (mergeWith.nested().isNested()) {
throw new IllegalArgumentException("object mapping [" + name() + "] can't be changed from non-nested to nested");
}
}
if (mergeWith.dynamic != null) {
this.dynamic = mergeWith.dynamic;
}
for (Mapper mergeWithMapper : mergeWith) {
Mapper mergeIntoMapper = mappers.get(mergeWithMapper.simpleName());
Mapper merged;
if (mergeIntoMapper == null) {
// no mapping, simply add it
merged = mergeWithMapper;
} else {
// root mappers can only exist here for backcompat, and are merged in Mapping
merged = mergeIntoMapper.merge(mergeWithMapper, updateAllTypes);
}
putMapper(merged);
}
}
@Override
public ObjectMapper updateFieldType(Map<String, MappedFieldType> fullNameToFieldType) {
List<Mapper> updatedMappers = null;
for (Mapper mapper : this) {
Mapper updated = mapper.updateFieldType(fullNameToFieldType);
if (mapper != updated) {
if (updatedMappers == null) {
updatedMappers = new ArrayList<>();
}
updatedMappers.add(updated);
}
}
if (updatedMappers == null) {
return this;
}
ObjectMapper updated = clone();
for (Mapper updatedMapper : updatedMappers) {
updated.putMapper(updatedMapper);
}
return updated;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
toXContent(builder, params, null);
return builder;
}
public void toXContent(XContentBuilder builder, Params params, ToXContent custom) throws IOException {
builder.startObject(simpleName());
if (nested.isNested()) {
builder.field("type", NESTED_CONTENT_TYPE);
if (nested.isIncludeInParent()) {
builder.field("include_in_parent", true);
}
if (nested.isIncludeInRoot()) {
builder.field("include_in_root", true);
}
} else if (mappers.isEmpty() && custom == null) { // only write the object content type if there are no properties, otherwise, it is automatically detected
builder.field("type", CONTENT_TYPE);
}
if (cqlStruct != Defaults.CQL_STRUCT) {
if (cqlStruct.equals(CqlStruct.MAP)) {
builder.field(TypeParsers.CQL_STRUCT, "map");
} else if (cqlStruct.equals(CqlStruct.UDT)) {
builder.field(TypeParsers.CQL_STRUCT, "udt");
} else if (cqlStruct.equals(CqlStruct.TUPLE)) {
builder.field(TypeParsers.CQL_STRUCT, "tuple");
}
}
if (cqlCollection != Defaults.CQL_COLLECTION) {
if (cqlCollection.equals(CqlCollection.SET)) {
builder.field(TypeParsers.CQL_COLLECTION, "set");
} else if (cqlCollection.equals(CqlCollection.LIST)) {
builder.field(TypeParsers.CQL_COLLECTION, "list");
} else if (cqlCollection.equals(CqlCollection.SINGLETON)) {
builder.field(TypeParsers.CQL_COLLECTION, "singleton");
}
}
if (cqlUdtName != null) {
builder.field(TypeParsers.CQL_UDT_NAME, cqlUdtName);
}
if (cqlPartialUpdate != Defaults.CQL_MANDATORY) {
builder.field(TypeParsers.CQL_MANDATORY, cqlPartialUpdate);
}
if (cqlPartitionKey != Defaults.CQL_PARTITION_KEY) {
builder.field(TypeParsers.CQL_PARTITION_KEY, cqlPartitionKey);
}
if (cqlPrimaryKeyOrder != Defaults.CQL_PRIMARY_KEY_ORDER) {
builder.field(TypeParsers.CQL_PRIMARY_KEY_ORDER, cqlPrimaryKeyOrder);
}
if (cqlStaticColumn != Defaults.CQL_STATIC_COLUMN) {
builder.field(TypeParsers.CQL_STATIC_COLUMN, cqlStaticColumn);
}
if (dynamic != null) {
builder.field("dynamic", dynamic.name().toLowerCase(Locale.ROOT));
}
if (enabled != Defaults.ENABLED) {
builder.field("enabled", enabled);
}
if (pathType != Defaults.PATH_TYPE) {
builder.field("path", pathType.name().toLowerCase(Locale.ROOT));
}
if (includeInAll != null) {
builder.field("include_in_all", includeInAll);
}
if (custom != null) {
custom.toXContent(builder, params);
}
doXContent(builder, params);
// sort the mappers so we get consistent serialization format
Mapper[] sortedMappers = Iterables.toArray(mappers.values(), Mapper.class);
Arrays.sort(sortedMappers, new Comparator<Mapper>() {
@Override
public int compare(Mapper o1, Mapper o2) {
return o1.name().compareTo(o2.name());
}
});
int count = 0;
for (Mapper mapper : sortedMappers) {
if (!(mapper instanceof MetadataFieldMapper)) {
if (count++ == 0) {
builder.startObject("properties");
}
mapper.toXContent(builder, params);
}
}
if (count > 0) {
builder.endObject();
}
builder.endObject();
}
protected void doXContent(XContentBuilder builder, Params params) throws IOException {
}
public CqlCollection cqlCollection() {
return this.cqlCollection;
}
public String cqlCollectionTag() {
if (this.cqlCollection.equals(CqlCollection.LIST)) return "list";
if (this.cqlCollection.equals(CqlCollection.SET)) return "set";
return "";
}
public CqlStruct cqlStruct() {
return this.cqlStruct;
}
public String cqlUdtName() {
return this.cqlUdtName;
}
public boolean cqlPartialUpdate() {
return this.cqlPartialUpdate;
}
@Override
public boolean cqlPartitionKey() {
return this.cqlPartitionKey;
}
@Override
public int cqlPrimaryKeyOrder() {
return this.cqlPrimaryKeyOrder;
}
@Override
public boolean cqlStaticColumn() {
return this.cqlStaticColumn;
}
}