Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.elasticsearch.xcontent.XContentBuilder Maven / Gradle / Ivy
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.xcontent;
import org.elasticsearch.core.CheckedConsumer;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.core.RestApiVersion;
import org.elasticsearch.core.Streams;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.file.Path;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Base64;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.function.Function;
/**
* A utility to build XContent (ie json).
*/
public final class XContentBuilder implements Closeable, Flushable {
/**
* Create a new {@link XContentBuilder} using the given {@link XContent} content.
*
* The builder uses an internal {@link ByteArrayOutputStream} output stream to build the content.
*
*
* @param xContent the {@link XContent}
* @return a new {@link XContentBuilder}
* @throws IOException if an {@link IOException} occurs while building the content
*/
public static XContentBuilder builder(XContent xContent) throws IOException {
return new XContentBuilder(xContent, new ByteArrayOutputStream());
}
/**
* Create a new {@link XContentBuilder} using the given {@link XContent} content and RestApiVersion.
*
* The builder uses an internal {@link ByteArrayOutputStream} output stream to build the content.
*
*
* @param xContent the {@link XContent}
* @return a new {@link XContentBuilder}
* @throws IOException if an {@link IOException} occurs while building the content
*/
public static XContentBuilder builder(XContent xContent, RestApiVersion restApiVersion) throws IOException {
return new XContentBuilder(
xContent,
new ByteArrayOutputStream(),
Collections.emptySet(),
Collections.emptySet(),
xContent.type().toParsedMediaType(),
restApiVersion
);
}
/**
* Create a new {@link XContentBuilder} using the given {@link XContentType} xContentType and some inclusive and/or exclusive filters.
*
* The builder uses an internal {@link ByteArrayOutputStream} output stream to build the content. When both exclusive and
* inclusive filters are provided, the underlying builder will first use exclusion filters to remove fields and then will check the
* remaining fields against the inclusive filters.
*
* @param xContentType the {@link XContentType}
* @param includes the inclusive filters: only fields and objects that match the inclusive filters will be written to the output.
* @param excludes the exclusive filters: only fields and objects that don't match the exclusive filters will be written to the output.
* @throws IOException if an {@link IOException} occurs while building the content
*/
public static XContentBuilder builder(XContentType xContentType, Set includes, Set excludes) throws IOException {
return new XContentBuilder(
xContentType.xContent(),
new ByteArrayOutputStream(),
includes,
excludes,
xContentType.toParsedMediaType(),
RestApiVersion.current()
);
}
private static final Map, Writer> WRITERS;
private static final Map, HumanReadableTransformer> HUMAN_READABLE_TRANSFORMERS;
private static final Map, Function> DATE_TRANSFORMERS;
static {
Map, Writer> writers = new HashMap<>();
writers.put(Boolean.class, (b, v) -> b.value((Boolean) v));
writers.put(boolean[].class, (b, v) -> b.values((boolean[]) v));
writers.put(Byte.class, (b, v) -> b.value((Byte) v));
writers.put(byte[].class, (b, v) -> b.value((byte[]) v));
writers.put(Date.class, XContentBuilder::timeValue);
writers.put(Double.class, (b, v) -> b.value((Double) v));
writers.put(double[].class, (b, v) -> b.values((double[]) v));
writers.put(Float.class, (b, v) -> b.value((Float) v));
writers.put(float[].class, (b, v) -> b.values((float[]) v));
writers.put(Integer.class, (b, v) -> b.value((Integer) v));
writers.put(int[].class, (b, v) -> b.values((int[]) v));
writers.put(Long.class, (b, v) -> b.value((Long) v));
writers.put(long[].class, (b, v) -> b.values((long[]) v));
writers.put(Short.class, (b, v) -> b.value((Short) v));
writers.put(short[].class, (b, v) -> b.values((short[]) v));
writers.put(String.class, (b, v) -> b.value((String) v));
writers.put(String[].class, (b, v) -> b.values((String[]) v));
writers.put(Locale.class, (b, v) -> b.value(v.toString()));
writers.put(Class.class, (b, v) -> b.value(v.toString()));
writers.put(ZonedDateTime.class, (b, v) -> b.value(v.toString()));
writers.put(Calendar.class, XContentBuilder::timeValue);
writers.put(GregorianCalendar.class, XContentBuilder::timeValue);
writers.put(BigInteger.class, (b, v) -> b.value((BigInteger) v));
writers.put(BigDecimal.class, (b, v) -> b.value((BigDecimal) v));
Map, HumanReadableTransformer> humanReadableTransformer = new HashMap<>();
Map, Function> dateTransformers = new HashMap<>();
// treat strings as already converted
dateTransformers.put(String.class, Function.identity());
// Load pluggable extensions
for (XContentBuilderExtension service : ServiceLoader.load(XContentBuilderExtension.class)) {
Map, Writer> addlWriters = service.getXContentWriters();
Map, HumanReadableTransformer> addlTransformers = service.getXContentHumanReadableTransformers();
Map, Function> addlDateTransformers = service.getDateTransformers();
addlWriters.forEach((key, value) -> Objects.requireNonNull(value, "invalid null xcontent writer for class " + key));
addlTransformers.forEach(
(key, value) -> Objects.requireNonNull(value, "invalid null xcontent transformer for human readable class " + key)
);
dateTransformers.forEach(
(key, value) -> Objects.requireNonNull(value, "invalid null xcontent date transformer for class " + key)
);
writers.putAll(addlWriters);
humanReadableTransformer.putAll(addlTransformers);
dateTransformers.putAll(addlDateTransformers);
}
WRITERS = Map.copyOf(writers);
HUMAN_READABLE_TRANSFORMERS = Map.copyOf(humanReadableTransformer);
DATE_TRANSFORMERS = Map.copyOf(dateTransformers);
}
@FunctionalInterface
public interface Writer {
void write(XContentBuilder builder, Object value) throws IOException;
}
/**
* Interface for transforming complex objects into their "raw" equivalents for human-readable fields
*/
@FunctionalInterface
public interface HumanReadableTransformer {
Object rawValue(Object value) throws IOException;
}
/**
* XContentGenerator used to build the XContent object
*/
private final XContentGenerator generator;
/**
* Output stream to which the built object is written
*/
private final OutputStream bos;
private final RestApiVersion restApiVersion;
private final ParsedMediaType responseContentType;
/**
* When this flag is set to true, some types of values are written in a format easier to read for a human.
*/
private boolean humanReadable = false;
/**
* Constructs a new builder using the provided XContent and an OutputStream. Make sure
* to call {@link #close()} when the builder is done with.
*/
public XContentBuilder(XContent xContent, OutputStream bos) throws IOException {
this(xContent, bos, Collections.emptySet(), Collections.emptySet(), xContent.type().toParsedMediaType(), RestApiVersion.current());
}
/**
* Constructs a new builder using the provided XContent, an OutputStream and
* some filters. If filters are specified, only those values matching a
* filter will be written to the output stream. Make sure to call
* {@link #close()} when the builder is done with.
*/
public XContentBuilder(XContentType xContentType, OutputStream bos, Set includes) throws IOException {
this(xContentType.xContent(), bos, includes, Collections.emptySet(), xContentType.toParsedMediaType(), RestApiVersion.current());
}
/**
* Creates a new builder using the provided XContent, output stream and some inclusive and/or exclusive filters. When both exclusive and
* inclusive filters are provided, the underlying builder will first use exclusion filters to remove fields and then will check the
* remaining fields against the inclusive filters.
*
* Make sure to call {@link #close()} when the builder is done with.
* @param os the output stream
* @param includes the inclusive filters: only fields and objects that match the inclusive filters will be written to the output.
* @param excludes the exclusive filters: only fields and objects that don't match the exclusive filters will be written to the output.
* @param responseContentType a content-type header value to be sent back on a response
*/
public XContentBuilder(
XContent xContent,
OutputStream os,
Set includes,
Set excludes,
ParsedMediaType responseContentType
) throws IOException {
this(xContent, os, includes, excludes, responseContentType, RestApiVersion.current());
}
/**
* Creates a new builder using the provided XContent, output stream and some inclusive and/or exclusive filters. When both exclusive and
* inclusive filters are provided, the underlying builder will first use exclusion filters to remove fields and then will check the
* remaining fields against the inclusive filters.
* Stores RestApiVersion to help steer the use of the builder depending on the version.
* @see #getRestApiVersion()
*
* Make sure to call {@link #close()} when the builder is done with.
* @param os the output stream
* @param includes the inclusive filters: only fields and objects that match the inclusive filters will be written to the output.
* @param excludes the exclusive filters: only fields and objects that don't match the exclusive filters will be written to the output.
* @param responseContentType a content-type header value to be sent back on a response
* @param restApiVersion a rest api version indicating with which version the XContent is compatible with.
*/
public XContentBuilder(
XContent xContent,
OutputStream os,
Set includes,
Set excludes,
ParsedMediaType responseContentType,
RestApiVersion restApiVersion
) throws IOException {
this.bos = os;
assert responseContentType != null : "generated response cannot be null";
this.responseContentType = responseContentType;
this.generator = xContent.createGenerator(bos, includes, excludes);
this.restApiVersion = restApiVersion;
}
public String getResponseContentTypeString() {
return responseContentType.responseContentTypeHeader();
}
public XContentType contentType() {
return generator.contentType();
}
/**
* @return the output stream to which the built object is being written. Note that is dangerous to modify the stream.
*/
public OutputStream getOutputStream() {
return bos;
}
public XContentBuilder prettyPrint() {
generator.usePrettyPrint();
return this;
}
public boolean isPrettyPrint() {
return generator.isPrettyPrint();
}
/**
* Indicate that the current {@link XContentBuilder} must write a line feed ("\n")
* at the end of the built object.
*
* This only applies for JSON XContent type. It has no effect for other types.
*/
public XContentBuilder lfAtEnd() {
generator.usePrintLineFeedAtEnd();
return this;
}
/**
* Set the "human readable" flag. Once set, some types of values are written in a
* format easier to read for a human.
*/
public XContentBuilder humanReadable(boolean isHumanReadable) {
this.humanReadable = isHumanReadable;
return this;
}
/**
* @return the value of the "human readable" flag. When the value is equal to true,
* some types of values are written in a format easier to read for a human.
*/
public boolean humanReadable() {
return this.humanReadable;
}
////////////////////////////////////////////////////////////////////////////
// Structure (object, array, field, null values...)
//////////////////////////////////
public XContentBuilder object(String name, CheckedConsumer callback) throws IOException {
field(name).startObject();
callback.accept(this);
return endObject();
}
public XContentBuilder startObject() throws IOException {
generator.writeStartObject();
return this;
}
public XContentBuilder startObject(String name) throws IOException {
return field(name).startObject();
}
public XContentBuilder endObject() throws IOException {
generator.writeEndObject();
return this;
}
public XContentBuilder array(String name, CheckedConsumer callback) throws IOException {
field(name).startArray();
callback.accept(this);
return endArray();
}
public XContentBuilder startArray() throws IOException {
generator.writeStartArray();
return this;
}
public XContentBuilder startArray(String name) throws IOException {
return field(name).startArray();
}
public XContentBuilder endArray() throws IOException {
generator.writeEndArray();
return this;
}
public XContentBuilder field(String name) throws IOException {
ensureNameNotNull(name);
generator.writeFieldName(name);
return this;
}
public XContentBuilder nullField(String name) throws IOException {
ensureNameNotNull(name);
generator.writeNullField(name);
return this;
}
public XContentBuilder nullValue() throws IOException {
generator.writeNull();
return this;
}
////////////////////////////////////////////////////////////////////////////
// Boolean
//////////////////////////////////
public XContentBuilder field(String name, Boolean value) throws IOException {
return (value == null) ? nullField(name) : field(name, value.booleanValue());
}
public XContentBuilder field(String name, boolean value) throws IOException {
ensureNameNotNull(name);
generator.writeBooleanField(name, value);
return this;
}
public XContentBuilder array(String name, boolean[] values) throws IOException {
return field(name).values(values);
}
private XContentBuilder values(boolean[] values) throws IOException {
if (values == null) {
return nullValue();
}
startArray();
for (boolean b : values) {
value(b);
}
endArray();
return this;
}
public XContentBuilder value(Boolean value) throws IOException {
return (value == null) ? nullValue() : value(value.booleanValue());
}
public XContentBuilder value(boolean value) throws IOException {
generator.writeBoolean(value);
return this;
}
////////////////////////////////////////////////////////////////////////////
// Byte
//////////////////////////////////
public XContentBuilder field(String name, Byte value) throws IOException {
return (value == null) ? nullField(name) : field(name, value.byteValue());
}
public XContentBuilder field(String name, byte value) throws IOException {
return field(name).value(value);
}
public XContentBuilder value(Byte value) throws IOException {
return (value == null) ? nullValue() : value(value.byteValue());
}
public XContentBuilder value(byte value) throws IOException {
generator.writeNumber(value);
return this;
}
public XContentBuilder array(String name, byte[] values) throws IOException {
return field(name).values(values);
}
private XContentBuilder values(byte[] values) throws IOException {
if (values == null) {
return nullValue();
}
startArray();
for (byte b : values) {
value(b);
}
endArray();
return this;
}
////////////////////////////////////////////////////////////////////////////
// Double
//////////////////////////////////
public XContentBuilder field(String name, Double value) throws IOException {
return (value == null) ? nullField(name) : field(name, value.doubleValue());
}
public XContentBuilder field(String name, double value) throws IOException {
ensureNameNotNull(name);
generator.writeNumberField(name, value);
return this;
}
public XContentBuilder array(String name, double[] values) throws IOException {
return field(name).values(values);
}
private XContentBuilder values(double[] values) throws IOException {
if (values == null) {
return nullValue();
}
startArray();
for (double b : values) {
value(b);
}
endArray();
return this;
}
public XContentBuilder value(Double value) throws IOException {
return (value == null) ? nullValue() : value(value.doubleValue());
}
public XContentBuilder value(double value) throws IOException {
generator.writeNumber(value);
return this;
}
////////////////////////////////////////////////////////////////////////////
// Float
//////////////////////////////////
public XContentBuilder field(String name, Float value) throws IOException {
return (value == null) ? nullField(name) : field(name, value.floatValue());
}
public XContentBuilder field(String name, float value) throws IOException {
ensureNameNotNull(name);
generator.writeNumberField(name, value);
return this;
}
public XContentBuilder array(String name, float[] values) throws IOException {
return field(name).values(values);
}
private XContentBuilder values(float[] values) throws IOException {
if (values == null) {
return nullValue();
}
startArray();
for (float f : values) {
value(f);
}
endArray();
return this;
}
public XContentBuilder value(Float value) throws IOException {
return (value == null) ? nullValue() : value(value.floatValue());
}
public XContentBuilder value(float value) throws IOException {
generator.writeNumber(value);
return this;
}
////////////////////////////////////////////////////////////////////////////
// Integer
//////////////////////////////////
public XContentBuilder field(String name, Integer value) throws IOException {
return (value == null) ? nullField(name) : field(name, value.intValue());
}
public XContentBuilder field(String name, int value) throws IOException {
ensureNameNotNull(name);
generator.writeNumberField(name, value);
return this;
}
public XContentBuilder array(String name, int[] values) throws IOException {
return field(name).values(values);
}
private XContentBuilder values(int[] values) throws IOException {
if (values == null) {
return nullValue();
}
startArray();
for (int i : values) {
value(i);
}
endArray();
return this;
}
public XContentBuilder value(Integer value) throws IOException {
return (value == null) ? nullValue() : value(value.intValue());
}
public XContentBuilder value(int value) throws IOException {
generator.writeNumber(value);
return this;
}
////////////////////////////////////////////////////////////////////////////
// Long
//////////////////////////////////
public XContentBuilder field(String name, Long value) throws IOException {
return (value == null) ? nullField(name) : field(name, value.longValue());
}
public XContentBuilder field(String name, long value) throws IOException {
ensureNameNotNull(name);
generator.writeNumberField(name, value);
return this;
}
public XContentBuilder array(String name, long[] values) throws IOException {
return field(name).values(values);
}
private XContentBuilder values(long[] values) throws IOException {
if (values == null) {
return nullValue();
}
startArray();
for (long l : values) {
value(l);
}
endArray();
return this;
}
public XContentBuilder value(Long value) throws IOException {
return (value == null) ? nullValue() : value(value.longValue());
}
public XContentBuilder value(long value) throws IOException {
generator.writeNumber(value);
return this;
}
////////////////////////////////////////////////////////////////////////////
// Short
//////////////////////////////////
public XContentBuilder field(String name, Short value) throws IOException {
return (value == null) ? nullField(name) : field(name, value.shortValue());
}
public XContentBuilder field(String name, short value) throws IOException {
return field(name).value(value);
}
public XContentBuilder array(String name, short[] values) throws IOException {
return field(name).values(values);
}
private XContentBuilder values(short[] values) throws IOException {
if (values == null) {
return nullValue();
}
startArray();
for (short s : values) {
value(s);
}
endArray();
return this;
}
public XContentBuilder value(Short value) throws IOException {
return (value == null) ? nullValue() : value(value.shortValue());
}
public XContentBuilder value(short value) throws IOException {
generator.writeNumber(value);
return this;
}
////////////////////////////////////////////////////////////////////////////
// BigInteger
//////////////////////////////////
public XContentBuilder field(String name, BigInteger value) throws IOException {
if (value == null) {
return nullField(name);
}
ensureNameNotNull(name);
generator.writeNumberField(name, value);
return this;
}
public XContentBuilder array(String name, BigInteger[] values) throws IOException {
return field(name).values(values);
}
private XContentBuilder values(BigInteger[] values) throws IOException {
if (values == null) {
return nullValue();
}
startArray();
for (BigInteger b : values) {
value(b);
}
endArray();
return this;
}
public XContentBuilder value(BigInteger value) throws IOException {
if (value == null) {
return nullValue();
}
generator.writeNumber(value);
return this;
}
////////////////////////////////////////////////////////////////////////////
// BigDecimal
//////////////////////////////////
public XContentBuilder field(String name, BigDecimal value) throws IOException {
if (value == null) {
return nullField(name);
}
ensureNameNotNull(name);
generator.writeNumberField(name, value);
return this;
}
public XContentBuilder array(String name, BigDecimal[] values) throws IOException {
return field(name).values(values);
}
private XContentBuilder values(BigDecimal[] values) throws IOException {
if (values == null) {
return nullValue();
}
startArray();
for (BigDecimal b : values) {
value(b);
}
endArray();
return this;
}
public XContentBuilder value(BigDecimal value) throws IOException {
if (value == null) {
return nullValue();
}
generator.writeNumber(value);
return this;
}
////////////////////////////////////////////////////////////////////////////
// String
//////////////////////////////////
public XContentBuilder field(String name, String value) throws IOException {
if (value == null) {
return nullField(name);
}
ensureNameNotNull(name);
generator.writeStringField(name, value);
return this;
}
public XContentBuilder array(String name, String... values) throws IOException {
return field(name).values(values);
}
private XContentBuilder values(String[] values) throws IOException {
if (values == null) {
return nullValue();
}
generator.writeStringArray(values);
return this;
}
public XContentBuilder value(String value) throws IOException {
if (value == null) {
return nullValue();
}
generator.writeString(value);
return this;
}
////////////////////////////////////////////////////////////////////////////
// Binary
//////////////////////////////////
public XContentBuilder field(String name, byte[] value) throws IOException {
if (value == null) {
return nullField(name);
}
ensureNameNotNull(name);
generator.writeBinaryField(name, value);
return this;
}
public XContentBuilder value(byte[] value) throws IOException {
if (value == null) {
return nullValue();
}
generator.writeBinary(value);
return this;
}
public XContentBuilder field(String name, byte[] value, int offset, int length) throws IOException {
return field(name).value(value, offset, length);
}
public XContentBuilder value(byte[] value, int offset, int length) throws IOException {
if (value == null) {
return nullValue();
}
generator.writeBinary(value, offset, length);
return this;
}
/**
* Writes the binary content of the given byte array as UTF-8 bytes.
*
* Use {@link XContentParser#charBuffer()} to read the value back
*/
public XContentBuilder utf8Value(byte[] bytes, int offset, int length) throws IOException {
generator.writeUTF8String(bytes, offset, length);
return this;
}
////////////////////////////////////////////////////////////////////////////
// Date
//////////////////////////////////
/**
* Write a time-based field and value, if the passed timeValue is null a
* null value is written, otherwise a date transformers lookup is performed.
* @throws IllegalArgumentException if there is no transformers for the type of object
*/
public XContentBuilder timeField(String name, Object timeValue) throws IOException {
return field(name).timeValue(timeValue);
}
/**
* If the {@code humanReadable} flag is set, writes both a formatted and
* unformatted version of the time value using the date transformer for the
* {@link Long} class.
*/
public XContentBuilder timeField(String name, String readableName, long value) throws IOException {
assert name.equals(readableName) == false : "expected raw and readable field names to differ, but they were both: " + name;
if (humanReadable) {
Function longTransformer = DATE_TRANSFORMERS.get(Long.class);
if (longTransformer == null) {
throw new IllegalArgumentException("cannot write time value xcontent for unknown value of type Long");
}
field(readableName).value(longTransformer.apply(value));
}
field(name, value);
return this;
}
/**
* Write a time-based value, if the value is null a null value is written,
* otherwise a date transformers lookup is performed.
* @throws IllegalArgumentException if there is no transformers for the type of object
*/
public XContentBuilder timeValue(Object timeValue) throws IOException {
if (timeValue == null) {
return nullValue();
} else {
Function transformer = DATE_TRANSFORMERS.get(timeValue.getClass());
if (transformer == null) {
throw new IllegalArgumentException("cannot write time value xcontent for unknown value of type " + timeValue.getClass());
}
return value(transformer.apply(timeValue));
}
}
////////////////////////////////////////////////////////////////////////////
// LatLon
//////////////////////////////////
public XContentBuilder latlon(String name, double lat, double lon) throws IOException {
return field(name).latlon(lat, lon);
}
public XContentBuilder latlon(double lat, double lon) throws IOException {
return startObject().field("lat", lat).field("lon", lon).endObject();
}
////////////////////////////////////////////////////////////////////////////
// Path
//////////////////////////////////
public XContentBuilder value(Path value) throws IOException {
if (value == null) {
return nullValue();
}
return value(value.toString());
}
////////////////////////////////////////////////////////////////////////////
// Objects
//
// These methods are used when the type of value is unknown. It tries to fallback
// on typed methods and use Object.toString() as a last resort. Always prefer using
// typed methods over this.
//////////////////////////////////
public XContentBuilder field(String name, Object value) throws IOException {
return field(name).value(value);
}
public XContentBuilder field(String name, Collection value) throws IOException {
return stringListField(name, value);
}
public XContentBuilder field(String name, String[] value) throws IOException {
return array(name, value);
}
public XContentBuilder field(String name, Number value) throws IOException {
field(name);
if (value instanceof Short) {
return value(value.shortValue());
} else if (value instanceof Integer) {
return value(value.intValue());
} else if (value instanceof Long) {
return value(value.longValue());
} else if (value instanceof Float) {
return value(value.floatValue());
} else if (value instanceof Double) {
return value(value.doubleValue());
} else if (value instanceof BigInteger) {
generator.writeNumber((BigInteger) value);
return this;
} else if (value instanceof BigDecimal) {
generator.writeNumber((BigDecimal) value);
return this;
} else {
return value(value);
}
}
public XContentBuilder array(String name, Object... values) throws IOException {
return field(name).values(values, true);
}
private XContentBuilder values(Object[] values, boolean ensureNoSelfReferences) throws IOException {
if (values == null) {
return nullValue();
}
return value(Arrays.asList(values), ensureNoSelfReferences);
}
public XContentBuilder value(Object value) throws IOException {
unknownValue(value, true);
return this;
}
private void unknownValue(Object value, boolean ensureNoSelfReferences) throws IOException {
if (value == null) {
nullValue();
return;
}
Writer writer = WRITERS.get(value.getClass());
if (writer != null) {
writer.write(this, value);
} else if (value instanceof Path) {
// Path implements Iterable and causes endless recursion and a StackOverFlow if treated as an Iterable here
value((Path) value);
} else if (value instanceof Map) {
@SuppressWarnings("unchecked")
final Map valueMap = (Map) value;
map(valueMap, ensureNoSelfReferences, true);
} else if (value instanceof Iterable) {
value((Iterable>) value, ensureNoSelfReferences);
} else if (value instanceof Object[]) {
values((Object[]) value, ensureNoSelfReferences);
} else if (value instanceof ToXContent) {
value((ToXContent) value);
} else if (value instanceof Enum>) {
// Write out the Enum toString
value(Objects.toString(value));
} else {
throw new IllegalArgumentException("cannot write xcontent for unknown value of type " + value.getClass());
}
}
////////////////////////////////////////////////////////////////////////////
// ToXContent
//////////////////////////////////
public XContentBuilder field(String name, ToXContent value) throws IOException {
return field(name).value(value);
}
public XContentBuilder field(String name, ToXContent value, ToXContent.Params params) throws IOException {
return field(name).value(value, params);
}
public XContentBuilder value(ToXContent value) throws IOException {
return value(value, ToXContent.EMPTY_PARAMS);
}
public XContentBuilder value(Map map) throws IOException {
return map(map);
}
public XContentBuilder value(ToXContent value, ToXContent.Params params) throws IOException {
if (value == null) {
return nullValue();
}
value.toXContent(this, params);
return this;
}
////////////////////////////////////////////////////////////////////////////
// Maps & Iterable
//////////////////////////////////
public XContentBuilder stringListField(String name, Collection values) throws IOException {
field(name);
if (values == null) {
return nullValue();
}
startArray();
for (String value : values) {
value(value);
}
endArray();
return this;
}
public XContentBuilder xContentList(String name, Collection extends ToXContent> values) throws IOException {
return xContentList(name, values, ToXContent.EMPTY_PARAMS);
}
public XContentBuilder xContentList(String name, Collection extends ToXContent> values, ToXContent.Params params) throws IOException {
field(name);
if (values == null) {
return nullValue();
}
startArray();
for (ToXContent value : values) {
value(value, params);
}
endArray();
return this;
}
public XContentBuilder xContentList(String name, ToXContent... values) throws IOException {
field(name);
if (values == null) {
return nullValue();
}
startArray();
for (ToXContent value : values) {
value(value);
}
endArray();
return this;
}
public XContentBuilder enumSet(String name, EnumSet> values) throws IOException {
field(name);
if (values == null) {
return nullValue();
}
startArray();
for (Enum> value : values) {
value(value);
}
endArray();
return this;
}
public XContentBuilder field(String name, Map values) throws IOException {
return field(name).map(values);
}
public XContentBuilder map(Map values) throws IOException {
return map(values, true, true);
}
public XContentBuilder map(Map values, boolean ensureNoSelfReferences) throws IOException {
return map(values, ensureNoSelfReferences, true);
}
public XContentBuilder stringStringMap(String name, Map values) throws IOException {
field(name);
if (values == null) {
return nullValue();
}
startObject();
for (Map.Entry value : values.entrySet()) {
generator.writeStringField(value.getKey(), value.getValue());
}
return endObject();
}
public XContentBuilder xContentValuesMap(String name, Map values) throws IOException {
field(name);
if (values == null) {
return nullValue();
}
startObject();
for (Map.Entry value : values.entrySet()) {
field(value.getKey());
value(value.getValue());
}
return endObject();
}
/** writes a map without the start object and end object headers */
public XContentBuilder mapContents(Map values) throws IOException {
return map(values, true, false);
}
private XContentBuilder map(Map values, boolean ensureNoSelfReferences, boolean writeStartAndEndHeaders) throws IOException {
if (values == null) {
return nullValue();
}
// checks that the map does not contain references to itself because
// iterating over map entries will cause a stackoverflow error
if (ensureNoSelfReferences) {
ensureNoSelfReferences(values);
}
if (writeStartAndEndHeaders) {
startObject();
}
for (Map.Entry value : values.entrySet()) {
field(value.getKey());
// pass ensureNoSelfReferences=false as we already performed the check at a higher level
unknownValue(value.getValue(), false);
}
if (writeStartAndEndHeaders) {
endObject();
}
return this;
}
public XContentBuilder field(String name, Iterable> values) throws IOException {
return field(name).value(values);
}
private XContentBuilder value(Iterable> values, boolean ensureNoSelfReferences) throws IOException {
if (values == null) {
return nullValue();
}
if (values instanceof Path) {
// treat as single value
value((Path) values);
} else {
// checks that the iterable does not contain references to itself because
// iterating over entries will cause a stackoverflow error
if (ensureNoSelfReferences) {
ensureNoSelfReferences(values);
}
startArray();
for (Object value : values) {
// pass ensureNoSelfReferences=false as we already performed the check at a higher level
unknownValue(value, false);
}
endArray();
}
return this;
}
////////////////////////////////////////////////////////////////////////////
// Human readable fields
//
// These are fields that have a "raw" value and a "human readable" value,
// such as time values or byte sizes. The human readable variant is only
// used if the humanReadable flag has been set
//////////////////////////////////
public XContentBuilder humanReadableField(String rawFieldName, String readableFieldName, Object value) throws IOException {
assert rawFieldName.equals(readableFieldName) == false
: "expected raw and readable field names to differ, but they were both: " + rawFieldName;
if (humanReadable) {
field(readableFieldName, Objects.toString(value));
}
HumanReadableTransformer transformer = HUMAN_READABLE_TRANSFORMERS.get(value.getClass());
if (transformer != null) {
Object rawValue = transformer.rawValue(value);
field(rawFieldName, rawValue);
} else {
throw new IllegalArgumentException("no raw transformer found for class " + value.getClass());
}
return this;
}
////////////////////////////////////////////////////////////////////////////
// Misc.
//////////////////////////////////
public XContentBuilder percentageField(String rawFieldName, String readableFieldName, double percentage) throws IOException {
assert rawFieldName.equals(readableFieldName) == false
: "expected raw and readable field names to differ, but they were both: " + rawFieldName;
if (humanReadable) {
field(readableFieldName, String.format(Locale.ROOT, "%1.1f%%", percentage));
}
field(rawFieldName, percentage);
return this;
}
public XContentBuilder field(String name, Enum> value) throws IOException {
field(name);
return value(value == null ? null : value.toString());
}
////////////////////////////////////////////////////////////////////////////
// Raw fields
//////////////////////////////////
/**
* Writes a raw field with the value taken from the bytes in the stream
* @deprecated use {@link #rawField(String, InputStream, XContentType)} to avoid content type auto-detection
*/
@Deprecated
public XContentBuilder rawField(String name, InputStream value) throws IOException {
generator.writeRawField(name, value);
return this;
}
/**
* Writes a raw field with the value taken from the bytes in the stream
*/
public XContentBuilder rawField(String name, InputStream value, XContentType contentType) throws IOException {
generator.writeRawField(name, value, contentType);
return this;
}
/**
* Writes a value with the source coming directly from the bytes in the stream
*/
public XContentBuilder rawValue(InputStream stream, XContentType contentType) throws IOException {
generator.writeRawValue(stream, contentType);
return this;
}
/**
* Writes a value with the source coming directly from a pre-rendered string representation
*/
public XContentBuilder rawValue(String value) throws IOException {
generator.writeRawValue(value);
return this;
}
/**
* Copies current event from parser into this builder.
* The difference with {@link XContentBuilder#copyCurrentStructure(XContentParser)}
* is that this method does not copy sub-objects as a single entity.
* @param parser
* @throws IOException
*/
public XContentBuilder copyCurrentEvent(XContentParser parser) throws IOException {
generator.copyCurrentEvent(parser);
return this;
}
public XContentBuilder copyCurrentStructure(XContentParser parser) throws IOException {
generator.copyCurrentStructure(parser);
return this;
}
/**
* Write the content that is written to the output stream by the {@code writer} as a string encoded in Base64 format.
* This API can be used to generate XContent directly without the intermediate results to reduce memory usage.
* Note that this method supports only JSON.
*/
public XContentBuilder directFieldAsBase64(String name, CheckedConsumer writer) throws IOException {
if (contentType() != XContentType.JSON) {
assert false : "directFieldAsBase64 supports only JSON format";
throw new UnsupportedOperationException("directFieldAsBase64 supports only JSON format");
}
generator.writeDirectField(name, os -> {
os.write('\"');
// We need to close the output stream that is wrapped by a Base64 encoder to flush the outstanding buffer
// of the encoder, but we must not close the underlying output stream of the XContentBuilder.
final OutputStream noClose = Streams.noCloseStream(os);
final OutputStream encodedOutput = Base64.getEncoder().wrap(noClose);
writer.accept(encodedOutput);
encodedOutput.close(); // close to flush the outstanding buffer used in the Base64 Encoder
os.write('\"');
});
return this;
}
/**
* Returns a version used for serialising a response.
* @return a compatible version
*/
public RestApiVersion getRestApiVersion() {
return restApiVersion;
}
@Override
public void flush() throws IOException {
generator.flush();
}
@Override
public void close() {
try {
generator.close();
} catch (IOException e) {
throw new IllegalStateException("Failed to close the XContentBuilder", e);
}
}
public XContentGenerator generator() {
return this.generator;
}
public static void ensureNameNotNull(String name) {
ensureNotNull(name, "Field name cannot be null");
}
public static void ensureNotNull(Object value, String message) {
if (value == null) {
throw new IllegalArgumentException(message);
}
}
private static void ensureNoSelfReferences(Object value) {
Iterable> it = convert(value);
if (it != null) {
ensureNoSelfReferences(it, value, Collections.newSetFromMap(new IdentityHashMap<>()));
}
}
private static Iterable> convert(Object value) {
if (value == null) {
return null;
}
if (value instanceof Map) {
return ((Map, ?>) value).values();
} else if ((value instanceof Iterable) && (value instanceof Path == false)) {
return (Iterable>) value;
} else if (value instanceof Object[]) {
return Arrays.asList((Object[]) value);
} else {
return null;
}
}
private static void ensureNoSelfReferences(final Iterable> value, Object originalReference, final Set ancestors) {
if (value != null) {
if (ancestors.add(originalReference) == false) {
throw new IllegalArgumentException("Iterable object is self-referencing itself");
}
for (Object o : value) {
ensureNoSelfReferences(convert(o), o, ancestors);
}
ancestors.remove(originalReference);
}
}
/**
* Checks whether the given value is writeable as x-content.
*
* If the value cannot be passed to {@link #value(Object)} without
* error, an error {@link IllegalArgumentException} is thrown.
*/
public static void ensureToXContentable(@Nullable Object value) {
if (value == null) {
return;
}
if (value instanceof Object[] array) {
for (Object v : array) {
ensureToXContentable(v);
}
} else if (value instanceof Map, ?> map) {
for (Map.Entry, ?> entry : map.entrySet()) {
if (entry.getKey() instanceof String == false) {
throw new IllegalArgumentException(
"Cannot write non-String map key [" + entry.getKey().getClass().getName() + "] to x-content"
);
}
ensureToXContentable(entry.getValue());
}
} else if (value instanceof Path) {
// Path implements Iterable and causes endless recursion and a StackOverFlow if treated as an Iterable here
return;
} else if (value instanceof Iterable> iterable) {
// Iterable also implicitly handles Set and List
for (Object v : iterable) {
ensureToXContentable(v);
}
} else if (value instanceof ToXContent || value instanceof Enum>) {
return;
} else {
Class> type = value.getClass();
if (WRITERS.containsKey(type) == false) {
throw new IllegalArgumentException("Cannot write type [" + type.getCanonicalName() + "] to x-content");
}
}
}
}