Add exclusion filters support to filter_path

This commit adds the support for exclusion filter to the response filtering (filter_path) feature. It changes the XContentBuilder APIs so that it now accepts two types of filters: inclusive and exclusive. Filters are no more String arrays but sets of String instead.
This commit is contained in:
Tanguy Leroux 2016-08-08 11:43:56 +02:00
parent 1925813e09
commit b4245c7ad9
19 changed files with 558 additions and 201 deletions

View File

@ -19,12 +19,15 @@
package org.elasticsearch.common.xcontent; package org.elasticsearch.common.xcontent;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Reader; import java.io.Reader;
import java.util.Collections;
import java.util.Set;
/** /**
* A generic abstraction on top of handling content, inspired by JSON and pull parsing. * A generic abstraction on top of handling content, inspired by JSON and pull parsing.
@ -42,27 +45,20 @@ public interface XContent {
* Creates a new generator using the provided output stream. * Creates a new generator using the provided output stream.
*/ */
default XContentGenerator createGenerator(OutputStream os) throws IOException { default XContentGenerator createGenerator(OutputStream os) throws IOException {
return createGenerator(os, null, true); return createGenerator(os, Collections.emptySet(), Collections.emptySet());
} }
/** /**
* Creates a new generator using the provided output stream and some * Creates a new generator using the provided output stream and some inclusive and/or exclusive filters. When both exclusive and
* inclusive filters. Same as createGenerator(os, filters, true). * inclusive filters are provided, the underlying generator will first use exclusion filters to remove fields and then will check the
*/ * remaining fields against the inclusive filters.
default XContentGenerator createGenerator(OutputStream os, String[] filters) throws IOException {
return createGenerator(os, filters, true);
}
/**
* Creates a new generator using the provided output stream and some
* filters.
* *
* @param inclusive * @param os the output stream
* If true only paths matching a filter will be included in * @param includes the inclusive filters: only fields and objects that match the inclusive filters will be written to the output.
* output. If false no path matching a filter will be included in * @param excludes the exclusive filters: only fields and objects that don't match the exclusive filters will be written to the output.
* output
*/ */
XContentGenerator createGenerator(OutputStream os, String[] filters, boolean inclusive) throws IOException; XContentGenerator createGenerator(OutputStream os, Set<String> includes, Set<String> excludes) throws IOException;
/** /**
* Creates a parser over the provided string content. * Creates a parser over the provided string content.
*/ */

View File

@ -19,21 +19,8 @@
package org.elasticsearch.common.xcontent; package org.elasticsearch.common.xcontent;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.Path;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.io.BytesStream; import org.elasticsearch.common.io.BytesStream;
@ -47,6 +34,21 @@ import org.joda.time.ReadableInstant;
import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat; import org.joda.time.format.ISODateTimeFormat;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.Path;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/** /**
* A utility to build XContent (ie json). * A utility to build XContent (ie json).
*/ */
@ -58,12 +60,8 @@ public final class XContentBuilder implements BytesStream, Releasable {
return new XContentBuilder(xContent, new BytesStreamOutput()); return new XContentBuilder(xContent, new BytesStreamOutput());
} }
public static XContentBuilder builder(XContent xContent, String[] filters) throws IOException { public static XContentBuilder builder(XContent xContent, Set<String> includes, Set<String> excludes) throws IOException {
return new XContentBuilder(xContent, new BytesStreamOutput(), filters); return new XContentBuilder(xContent, new BytesStreamOutput(), includes, excludes);
}
public static XContentBuilder builder(XContent xContent, String[] filters, boolean inclusive) throws IOException {
return new XContentBuilder(xContent, new BytesStreamOutput(), filters, inclusive);
} }
private XContentGenerator generator; private XContentGenerator generator;
@ -77,7 +75,7 @@ public final class XContentBuilder implements BytesStream, Releasable {
* to call {@link #close()} when the builder is done with. * to call {@link #close()} when the builder is done with.
*/ */
public XContentBuilder(XContent xContent, OutputStream bos) throws IOException { public XContentBuilder(XContent xContent, OutputStream bos) throws IOException {
this(xContent, bos, null); this(xContent, bos, Collections.emptySet(), Collections.emptySet());
} }
/** /**
@ -86,20 +84,24 @@ public final class XContentBuilder implements BytesStream, Releasable {
* filter will be written to the output stream. Make sure to call * filter will be written to the output stream. Make sure to call
* {@link #close()} when the builder is done with. * {@link #close()} when the builder is done with.
*/ */
public XContentBuilder(XContent xContent, OutputStream bos, String[] filters) throws IOException { public XContentBuilder(XContent xContent, OutputStream bos, Set<String> includes) throws IOException {
this(xContent, bos, filters, true); this(xContent, bos, includes, Collections.emptySet());
} }
/** /**
* Constructs a new builder using the provided xcontent, an OutputStream and * Creates a new builder using the provided XContent, output stream and some inclusive and/or exclusive filters. When both exclusive and
* some filters. If {@code filters} are specified and {@code inclusive} is * inclusive filters are provided, the underlying builder will first use exclusion filters to remove fields and then will check the
* true, only those values matching a filter will be written to the output * remaining fields against the inclusive filters.
* stream. If {@code inclusive} is false, those matching will be excluded. * <p>
* Make sure to call {@link #close()} when the builder is done with. * 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.
*/ */
public XContentBuilder(XContent xContent, OutputStream bos, String[] filters, boolean inclusive) throws IOException { public XContentBuilder(XContent xContent, OutputStream os, Set<String> includes, Set<String> excludes) throws IOException {
this.bos = bos; this.bos = os;
this.generator = xContent.createGenerator(bos, filters, inclusive); this.generator = xContent.createGenerator(bos, includes, excludes);
} }
public XContentType contentType() { public XContentType contentType() {

View File

@ -35,6 +35,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Reader; import java.io.Reader;
import java.util.Set;
/** /**
* A CBOR based content implementation using Jackson. * A CBOR based content implementation using Jackson.
@ -70,8 +71,8 @@ public class CborXContent implements XContent {
} }
@Override @Override
public XContentGenerator createGenerator(OutputStream os, String[] filters, boolean inclusive) throws IOException { public XContentGenerator createGenerator(OutputStream os, Set<String> includes, Set<String> excludes) throws IOException {
return new CborXContentGenerator(cborFactory.createGenerator(os, JsonEncoding.UTF8), os, filters, inclusive); return new CborXContentGenerator(cborFactory.createGenerator(os, JsonEncoding.UTF8), os, includes, excludes);
} }
@Override @Override

View File

@ -20,23 +20,22 @@
package org.elasticsearch.common.xcontent.cbor; package org.elasticsearch.common.xcontent.cbor;
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonGenerator;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.json.JsonXContentGenerator; import org.elasticsearch.common.xcontent.json.JsonXContentGenerator;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Collections;
import java.util.Set;
/**
*
*/
public class CborXContentGenerator extends JsonXContentGenerator { public class CborXContentGenerator extends JsonXContentGenerator {
public CborXContentGenerator(JsonGenerator jsonGenerator, OutputStream os, String... filters) { public CborXContentGenerator(JsonGenerator jsonGenerator, OutputStream os) {
this(jsonGenerator, os, filters, true); this(jsonGenerator, os, Collections.emptySet(), Collections.emptySet());
} }
public CborXContentGenerator(JsonGenerator jsonGenerator, OutputStream os, String[] filters, boolean inclusive) { public CborXContentGenerator(JsonGenerator jsonGenerator, OutputStream os, Set<String> includes, Set<String> excludes) {
super(jsonGenerator, os, filters, inclusive); super(jsonGenerator, os, includes, excludes);
} }
@Override @Override

View File

@ -35,6 +35,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Reader; import java.io.Reader;
import java.util.Set;
/** /**
* A JSON based content implementation using Jackson. * A JSON based content implementation using Jackson.
@ -92,8 +93,8 @@ public class JsonXContent implements XContent {
} }
@Override @Override
public XContentGenerator createGenerator(OutputStream os, String[] filters, boolean inclusive) throws IOException { public XContentGenerator createGenerator(OutputStream os, Set<String> includes, Set<String> excludes) throws IOException {
return new JsonXContentGenerator(jsonFactory.createGenerator(os, JsonEncoding.UTF8), os, filters, inclusive); return new JsonXContentGenerator(jsonFactory.createGenerator(os, JsonEncoding.UTF8), os, includes, excludes);
} }
@Override @Override

View File

@ -27,10 +27,10 @@ import com.fasterxml.jackson.core.io.SerializedString;
import com.fasterxml.jackson.core.json.JsonWriteContext; import com.fasterxml.jackson.core.json.JsonWriteContext;
import com.fasterxml.jackson.core.util.DefaultIndenter; import com.fasterxml.jackson.core.util.DefaultIndenter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.core.util.JsonGeneratorDelegate;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.Streams; import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.util.CollectionUtils;
import org.elasticsearch.common.xcontent.XContent; import org.elasticsearch.common.xcontent.XContent;
import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentGenerator; import org.elasticsearch.common.xcontent.XContentGenerator;
@ -43,6 +43,9 @@ import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
/** /**
* *
@ -72,23 +75,38 @@ public class JsonXContentGenerator implements XContentGenerator {
private static final DefaultPrettyPrinter.Indenter INDENTER = new DefaultIndenter(" ", LF.getValue()); private static final DefaultPrettyPrinter.Indenter INDENTER = new DefaultIndenter(" ", LF.getValue());
private boolean prettyPrint = false; private boolean prettyPrint = false;
public JsonXContentGenerator(JsonGenerator jsonGenerator, OutputStream os, String[] filters, boolean inclusive) { public JsonXContentGenerator(JsonGenerator jsonGenerator, OutputStream os) {
this(jsonGenerator, os, Collections.emptySet(), Collections.emptySet());
}
public JsonXContentGenerator(JsonGenerator jsonGenerator, OutputStream os, Set<String> includes, Set<String> excludes) {
Objects.requireNonNull(includes, "Including filters must not be null");
Objects.requireNonNull(excludes, "Excluding filters must not be null");
this.os = os;
if (jsonGenerator instanceof GeneratorBase) { if (jsonGenerator instanceof GeneratorBase) {
this.base = (GeneratorBase) jsonGenerator; this.base = (GeneratorBase) jsonGenerator;
} else { } else {
this.base = null; this.base = null;
} }
if (CollectionUtils.isEmpty(filters)) { JsonGenerator generator = jsonGenerator;
this.generator = jsonGenerator;
this.filter = null; boolean hasExcludes = excludes.isEmpty() == false;
} else { if (hasExcludes) {
this.filter = new FilteringGeneratorDelegate(jsonGenerator, generator = new FilteringGeneratorDelegate(generator, new FilterPathBasedFilter(excludes, false), true, true);
new FilterPathBasedFilter(filters, inclusive), true, true);
this.generator = this.filter;
} }
this.os = os; boolean hasIncludes = includes.isEmpty() == false;
if (hasIncludes) {
generator = new FilteringGeneratorDelegate(generator, new FilterPathBasedFilter(includes, true), true, true);
}
if (hasExcludes || hasIncludes) {
this.filter = (FilteringGeneratorDelegate) generator;
} else {
this.filter = null;
}
this.generator = generator;
} }
@Override @Override
@ -122,23 +140,34 @@ public class JsonXContentGenerator implements XContentGenerator {
generator.writeEndArray(); generator.writeEndArray();
} }
protected boolean isFiltered() { private boolean isFiltered() {
return filter != null; return filter != null;
} }
protected boolean inRoot() { private JsonGenerator getLowLevelGenerator() {
if (isFiltered()) { if (isFiltered()) {
JsonStreamContext context = filter.getFilterContext(); JsonGenerator delegate = filter.getDelegate();
return ((context != null) && (context.inRoot() && context.getCurrentName() == null)); if (delegate instanceof JsonGeneratorDelegate) {
// In case of combined inclusion and exclusion filters, we have one and only one another delegating level
delegate = ((JsonGeneratorDelegate) delegate).getDelegate();
assert delegate instanceof JsonGeneratorDelegate == false;
} }
return false; return delegate;
}
return generator;
}
private boolean inRoot() {
JsonStreamContext context = generator.getOutputContext();
return ((context != null) && (context.inRoot() && context.getCurrentName() == null));
} }
@Override @Override
public void writeStartObject() throws IOException { public void writeStartObject() throws IOException {
if (isFiltered() && inRoot()) { if (inRoot()) {
// Bypass generator to always write the root start object // Use the low level generator to write the startObject so that the root
filter.getDelegate().writeStartObject(); // start object is always written even if a filtered generator is used
getLowLevelGenerator().writeStartObject();
return; return;
} }
generator.writeStartObject(); generator.writeStartObject();
@ -146,9 +175,10 @@ public class JsonXContentGenerator implements XContentGenerator {
@Override @Override
public void writeEndObject() throws IOException { public void writeEndObject() throws IOException {
if (isFiltered() && inRoot()) { if (inRoot()) {
// Bypass generator to always write the root end object // Use the low level generator to write the startObject so that the root
filter.getDelegate().writeEndObject(); // start object is always written even if a filtered generator is used
getLowLevelGenerator().writeEndObject();
return; return;
} }
generator.writeEndObject(); generator.writeEndObject();
@ -390,7 +420,8 @@ public class JsonXContentGenerator implements XContentGenerator {
} }
if (writeLineFeedAtEnd) { if (writeLineFeedAtEnd) {
flush(); flush();
generator.writeRaw(LF); // Bypass generator to always write the line feed
getLowLevelGenerator().writeRaw(LF);
} }
generator.close(); generator.close();
} }

View File

@ -35,6 +35,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Reader; import java.io.Reader;
import java.util.Set;
/** /**
* A Smile based content implementation using Jackson. * A Smile based content implementation using Jackson.
@ -71,8 +72,8 @@ public class SmileXContent implements XContent {
} }
@Override @Override
public XContentGenerator createGenerator(OutputStream os, String[] filters, boolean inclusive) throws IOException { public XContentGenerator createGenerator(OutputStream os, Set<String> includes, Set<String> excludes) throws IOException {
return new SmileXContentGenerator(smileFactory.createGenerator(os, JsonEncoding.UTF8), os, filters, inclusive); return new SmileXContentGenerator(smileFactory.createGenerator(os, JsonEncoding.UTF8), os, includes, excludes);
} }
@Override @Override

View File

@ -20,23 +20,22 @@
package org.elasticsearch.common.xcontent.smile; package org.elasticsearch.common.xcontent.smile;
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonGenerator;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.json.JsonXContentGenerator; import org.elasticsearch.common.xcontent.json.JsonXContentGenerator;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Collections;
import java.util.Set;
/**
*
*/
public class SmileXContentGenerator extends JsonXContentGenerator { public class SmileXContentGenerator extends JsonXContentGenerator {
public SmileXContentGenerator(JsonGenerator jsonGenerator, OutputStream os, String... filters) { public SmileXContentGenerator(JsonGenerator jsonGenerator, OutputStream os) {
this(jsonGenerator, os, filters, true); this(jsonGenerator, os, Collections.emptySet(), Collections.emptySet());
} }
public SmileXContentGenerator(JsonGenerator jsonGenerator, OutputStream os, String[] filters, boolean inclusive) { public SmileXContentGenerator(JsonGenerator jsonGenerator, OutputStream os, Set<String> includes, Set<String> excludes) {
super(jsonGenerator, os, filters, inclusive); super(jsonGenerator, os, includes, excludes);
} }
@Override @Override

View File

@ -21,10 +21,10 @@
package org.elasticsearch.common.xcontent.support.filtering; package org.elasticsearch.common.xcontent.support.filtering;
import org.elasticsearch.common.regex.Regex; import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.util.CollectionUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set;
public class FilterPath { public class FilterPath {
@ -75,8 +75,8 @@ public class FilterPath {
return next; return next;
} }
public static FilterPath[] compile(String... filters) { public static FilterPath[] compile(Set<String> filters) {
if (CollectionUtils.isEmpty(filters)) { if (filters == null || filters.isEmpty()) {
return null; return null;
} }

View File

@ -24,6 +24,7 @@ import org.elasticsearch.common.util.CollectionUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set;
public class FilterPathBasedFilter extends TokenFilter { public class FilterPathBasedFilter extends TokenFilter {
@ -53,7 +54,7 @@ public class FilterPathBasedFilter extends TokenFilter {
this.filters = filters; this.filters = filters;
} }
public FilterPathBasedFilter(String[] filters, boolean inclusive) { public FilterPathBasedFilter(Set<String> filters, boolean inclusive) {
this(FilterPath.compile(filters), inclusive); this(FilterPath.compile(filters), inclusive);
} }
@ -103,11 +104,6 @@ public class FilterPathBasedFilter extends TokenFilter {
@Override @Override
protected boolean _includeScalar() { protected boolean _includeScalar() {
for (FilterPath filter : filters) {
if (filter.matches()) {
return inclusive;
}
}
return !inclusive; return !inclusive;
} }
} }

View File

@ -34,6 +34,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Reader; import java.io.Reader;
import java.util.Set;
/** /**
* A YAML based content implementation using Jackson. * A YAML based content implementation using Jackson.
@ -66,8 +67,8 @@ public class YamlXContent implements XContent {
} }
@Override @Override
public XContentGenerator createGenerator(OutputStream os, String[] filters, boolean inclusive) throws IOException { public XContentGenerator createGenerator(OutputStream os, Set<String> includes, Set<String> excludes) throws IOException {
return new YamlXContentGenerator(yamlFactory.createGenerator(os, JsonEncoding.UTF8), os, filters, inclusive); return new YamlXContentGenerator(yamlFactory.createGenerator(os, JsonEncoding.UTF8), os, includes, excludes);
} }
@Override @Override

View File

@ -20,23 +20,22 @@
package org.elasticsearch.common.xcontent.yaml; package org.elasticsearch.common.xcontent.yaml;
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonGenerator;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.json.JsonXContentGenerator; import org.elasticsearch.common.xcontent.json.JsonXContentGenerator;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Collections;
import java.util.Set;
/**
*
*/
public class YamlXContentGenerator extends JsonXContentGenerator { public class YamlXContentGenerator extends JsonXContentGenerator {
public YamlXContentGenerator(JsonGenerator jsonGenerator, OutputStream os, String... filters) { public YamlXContentGenerator(JsonGenerator jsonGenerator, OutputStream os) {
this(jsonGenerator, os, filters, true); this(jsonGenerator, os, Collections.emptySet(), Collections.emptySet());
} }
public YamlXContentGenerator(JsonGenerator jsonGenerator, OutputStream os, String[] filters, boolean inclusive) { public YamlXContentGenerator(JsonGenerator jsonGenerator, OutputStream os, Set<String> includes, Set<String> excludes) {
super(jsonGenerator, os, filters, inclusive); super(jsonGenerator, os, includes, excludes);
} }
@Override @Override

View File

@ -19,6 +19,7 @@
package org.elasticsearch.rest; package org.elasticsearch.rest;
import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
@ -26,9 +27,17 @@ import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.Set;
import java.util.function.Predicate;
import static java.util.stream.Collectors.toSet;
public abstract class AbstractRestChannel implements RestChannel { public abstract class AbstractRestChannel implements RestChannel {
private static final Predicate<String> INCLUDE_FILTER = f -> f.charAt(0) != '-';
private static final Predicate<String> EXCLUDE_FILTER = INCLUDE_FILTER.negate();
protected final RestRequest request; protected final RestRequest request;
protected final boolean detailedErrorsEnabled; protected final boolean detailedErrorsEnabled;
@ -41,7 +50,7 @@ public abstract class AbstractRestChannel implements RestChannel {
@Override @Override
public XContentBuilder newBuilder() throws IOException { public XContentBuilder newBuilder() throws IOException {
return newBuilder(request.hasContent() ? request.content() : null, request.hasParam("filter_path")); return newBuilder(request.hasContent() ? request.content() : null, true);
} }
@Override @Override
@ -64,8 +73,15 @@ public abstract class AbstractRestChannel implements RestChannel {
contentType = XContentType.JSON; contentType = XContentType.JSON;
} }
String[] filters = useFiltering ? request.paramAsStringArrayOrEmptyIfAll("filter_path") : null; Set<String> includes = Collections.emptySet();
XContentBuilder builder = new XContentBuilder(XContentFactory.xContent(contentType), bytesOutput(), filters); Set<String> excludes = Collections.emptySet();
if (useFiltering) {
Set<String> filters = Strings.splitStringByCommaToSet(request.param("filter_path", null));
includes = filters.stream().filter(INCLUDE_FILTER).collect(toSet());
excludes = filters.stream().filter(EXCLUDE_FILTER).map(f -> f.substring(1)).collect(toSet());
}
XContentBuilder builder = new XContentBuilder(XContentFactory.xContent(contentType), bytesOutput(), includes, excludes);
if (request.paramAsBoolean("pretty", false)) { if (request.paramAsBoolean("pretty", false)) {
builder.prettyPrint().lfAtEnd(); builder.prettyPrint().lfAtEnd();
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.common.xcontent.support.filtering; package org.elasticsearch.common.xcontent.support.filtering;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.common.xcontent.XContent; import org.elasticsearch.common.xcontent.XContent;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentFactory;
@ -28,7 +29,11 @@ import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import java.io.IOException; import java.io.IOException;
import java.util.Set;
import java.util.function.Function;
import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.nullValue;
@ -86,12 +91,16 @@ public abstract class AbstractFilteringJsonGeneratorTestCase extends ESTestCase
return XContentBuilder.builder(getXContentType().xContent()); return XContentBuilder.builder(getXContentType().xContent());
} }
private XContentBuilder newXContentBuilder(String filter, boolean inclusive) throws IOException { private XContentBuilder newXContentBuilderWithIncludes(String filter) throws IOException {
return XContentBuilder.builder(getXContentType().xContent(), new String[] { filter }, inclusive); return newXContentBuilder(singleton(filter), emptySet());
} }
private XContentBuilder newXContentBuilder(String[] filters, boolean inclusive) throws IOException { private XContentBuilder newXContentBuilderWithExcludes(String filter) throws IOException {
return XContentBuilder.builder(getXContentType().xContent(), filters, inclusive); return newXContentBuilder(emptySet(), singleton(filter));
}
private XContentBuilder newXContentBuilder(Set<String> includes, Set<String> excludes) throws IOException {
return XContentBuilder.builder(getXContentType().xContent(), includes, excludes);
} }
/** /**
@ -173,20 +182,22 @@ public abstract class AbstractFilteringJsonGeneratorTestCase extends ESTestCase
return builder; return builder;
} }
/** /** Create a new {@link XContentBuilder} and use it to build the sample using the given inclusive filter **/
* Instanciates a new XContentBuilder with the given filters and builds a private XContentBuilder sampleWithIncludes(String filter) throws IOException {
* sample with it. return sample(newXContentBuilderWithIncludes(filter));
* @param inclusive
* Specifies if filters are inclusive or exclusive
*/
private XContentBuilder sample(String filter, boolean inclusive) throws IOException {
return sample(newXContentBuilder(filter, inclusive));
} }
private XContentBuilder sample(String[] filters, boolean inclusive) throws IOException { /** Create a new {@link XContentBuilder} and use it to build the sample using the given exclusive filter **/
return sample(newXContentBuilder(filters, inclusive)); private XContentBuilder sampleWithExcludes(String filter) throws IOException {
return sample(newXContentBuilderWithExcludes(filter));
} }
/** Create a new {@link XContentBuilder} and use it to build the sample using the given includes and exclusive filters **/
private XContentBuilder sampleWithFilters(Set<String> includes, Set<String> excludes) throws IOException {
return sample(newXContentBuilder(includes, excludes));
}
/** Create a new {@link XContentBuilder} and use it to build the sample **/
private XContentBuilder sample() throws IOException { private XContentBuilder sample() throws IOException {
return sample(newXContentBuilder()); return sample(newXContentBuilder());
} }
@ -195,23 +206,23 @@ public abstract class AbstractFilteringJsonGeneratorTestCase extends ESTestCase
XContentBuilder expected = sample(); XContentBuilder expected = sample();
assertXContentBuilder(expected, sample()); assertXContentBuilder(expected, sample());
assertXContentBuilder(expected, sample("*", true)); assertXContentBuilder(expected, sampleWithIncludes("*"));
assertXContentBuilder(expected, sample("**", true)); assertXContentBuilder(expected, sampleWithIncludes("**"));
assertXContentBuilder(expected, sample("xyz", false)); assertXContentBuilder(expected, sampleWithExcludes("xyz"));
} }
public void testNoMatch() throws Exception { public void testNoMatch() throws Exception {
XContentBuilder expected = newXContentBuilder().startObject().endObject(); XContentBuilder expected = newXContentBuilder().startObject().endObject();
assertXContentBuilder(expected, sample("xyz", true)); assertXContentBuilder(expected, sampleWithIncludes("xyz"));
assertXContentBuilder(expected, sample("*", false)); assertXContentBuilder(expected, sampleWithExcludes("*"));
assertXContentBuilder(expected, sample("**", false)); assertXContentBuilder(expected, sampleWithExcludes("**"));
} }
public void testSimpleFieldInclusive() throws Exception { public void testSimpleFieldInclusive() throws Exception {
XContentBuilder expected = newXContentBuilder().startObject().field("title", "My awesome book").endObject(); XContentBuilder expected = newXContentBuilder().startObject().field("title", "My awesome book").endObject();
assertXContentBuilder(expected, sample("title", true)); assertXContentBuilder(expected, sampleWithIncludes("title"));
} }
public void testSimpleFieldExclusive() throws Exception { public void testSimpleFieldExclusive() throws Exception {
@ -286,10 +297,9 @@ public abstract class AbstractFilteringJsonGeneratorTestCase extends ESTestCase
.endObject() .endObject()
.endObject(); .endObject();
assertXContentBuilder(expected, sample("title", false)); assertXContentBuilder(expected, sampleWithExcludes("title"));
} }
public void testSimpleFieldWithWildcardInclusive() throws Exception { public void testSimpleFieldWithWildcardInclusive() throws Exception {
XContentBuilder expected = newXContentBuilder().startObject() XContentBuilder expected = newXContentBuilder().startObject()
.field("price", 27.99) .field("price", 27.99)
@ -343,7 +353,7 @@ public abstract class AbstractFilteringJsonGeneratorTestCase extends ESTestCase
.endObject() .endObject()
.endObject(); .endObject();
assertXContentBuilder(expected, sample("pr*", true)); assertXContentBuilder(expected, sampleWithIncludes("pr*"));
} }
public void testSimpleFieldWithWildcardExclusive() throws Exception { public void testSimpleFieldWithWildcardExclusive() throws Exception {
@ -370,7 +380,7 @@ public abstract class AbstractFilteringJsonGeneratorTestCase extends ESTestCase
.endArray() .endArray()
.endObject(); .endObject();
assertXContentBuilder(expected, sample("pr*", false)); assertXContentBuilder(expected, sampleWithExcludes("pr*"));
} }
public void testMultipleFieldsInclusive() throws Exception { public void testMultipleFieldsInclusive() throws Exception {
@ -379,7 +389,7 @@ public abstract class AbstractFilteringJsonGeneratorTestCase extends ESTestCase
.field("pages", 456) .field("pages", 456)
.endObject(); .endObject();
assertXContentBuilder(expected, sample(new String[] { "title", "pages" }, true)); assertXContentBuilder(expected, sampleWithFilters(Sets.newHashSet("title", "pages"), emptySet()));
} }
public void testMultipleFieldsExclusive() throws Exception { public void testMultipleFieldsExclusive() throws Exception {
@ -453,10 +463,9 @@ public abstract class AbstractFilteringJsonGeneratorTestCase extends ESTestCase
.endObject() .endObject()
.endObject(); .endObject();
assertXContentBuilder(expected, sample(new String[] { "title", "pages" }, false)); assertXContentBuilder(expected, sample(newXContentBuilder(emptySet(), Sets.newHashSet("title", "pages"))));
} }
public void testSimpleArrayInclusive() throws Exception { public void testSimpleArrayInclusive() throws Exception {
XContentBuilder expected = newXContentBuilder().startObject() XContentBuilder expected = newXContentBuilder().startObject()
.startArray("tags") .startArray("tags")
@ -465,7 +474,7 @@ public abstract class AbstractFilteringJsonGeneratorTestCase extends ESTestCase
.endArray() .endArray()
.endObject(); .endObject();
assertXContentBuilder(expected, sample("tags", true)); assertXContentBuilder(expected, sampleWithIncludes("tags"));
} }
public void testSimpleArrayExclusive() throws Exception { public void testSimpleArrayExclusive() throws Exception {
@ -537,10 +546,9 @@ public abstract class AbstractFilteringJsonGeneratorTestCase extends ESTestCase
.endObject() .endObject()
.endObject(); .endObject();
assertXContentBuilder(expected, sample("tags", false)); assertXContentBuilder(expected, sampleWithExcludes("tags"));
} }
public void testSimpleArrayOfObjectsInclusive() throws Exception { public void testSimpleArrayOfObjectsInclusive() throws Exception {
XContentBuilder expected = newXContentBuilder().startObject() XContentBuilder expected = newXContentBuilder().startObject()
.startArray("authors") .startArray("authors")
@ -557,9 +565,9 @@ public abstract class AbstractFilteringJsonGeneratorTestCase extends ESTestCase
.endArray() .endArray()
.endObject(); .endObject();
assertXContentBuilder(expected, sample("authors", true)); assertXContentBuilder(expected, sampleWithIncludes("authors"));
assertXContentBuilder(expected, sample("authors.*", true)); assertXContentBuilder(expected, sampleWithIncludes("authors.*"));
assertXContentBuilder(expected, sample("authors.*name", true)); assertXContentBuilder(expected, sampleWithIncludes("authors.*name"));
} }
public void testSimpleArrayOfObjectsExclusive() throws Exception { public void testSimpleArrayOfObjectsExclusive() throws Exception {
@ -623,9 +631,9 @@ public abstract class AbstractFilteringJsonGeneratorTestCase extends ESTestCase
.endObject() .endObject()
.endObject(); .endObject();
assertXContentBuilder(expected, sample("authors", false)); assertXContentBuilder(expected, sampleWithExcludes("authors"));
assertXContentBuilder(expected, sample("authors.*", false)); assertXContentBuilder(expected, sampleWithExcludes("authors.*"));
assertXContentBuilder(expected, sample("authors.*name", false)); assertXContentBuilder(expected, sampleWithExcludes("authors.*name"));
} }
public void testSimpleArrayOfObjectsPropertyInclusive() throws Exception { public void testSimpleArrayOfObjectsPropertyInclusive() throws Exception {
@ -640,8 +648,8 @@ public abstract class AbstractFilteringJsonGeneratorTestCase extends ESTestCase
.endArray() .endArray()
.endObject(); .endObject();
assertXContentBuilder(expected, sample("authors.lastname", true)); assertXContentBuilder(expected, sampleWithIncludes("authors.lastname"));
assertXContentBuilder(expected, sample("authors.l*", true)); assertXContentBuilder(expected, sampleWithIncludes("authors.l*"));
} }
public void testSimpleArrayOfObjectsPropertyExclusive() throws Exception { public void testSimpleArrayOfObjectsPropertyExclusive() throws Exception {
@ -715,8 +723,8 @@ public abstract class AbstractFilteringJsonGeneratorTestCase extends ESTestCase
.endObject() .endObject()
.endObject(); .endObject();
assertXContentBuilder(expected, sample("authors.lastname", false)); assertXContentBuilder(expected, sampleWithExcludes("authors.lastname"));
assertXContentBuilder(expected, sample("authors.l*", false)); assertXContentBuilder(expected, sampleWithExcludes("authors.l*"));
} }
public void testRecurseField1Inclusive() throws Exception { public void testRecurseField1Inclusive() throws Exception {
@ -768,7 +776,7 @@ public abstract class AbstractFilteringJsonGeneratorTestCase extends ESTestCase
.endObject() .endObject()
.endObject(); .endObject();
assertXContentBuilder(expected, sample("**.name", true)); assertXContentBuilder(expected, sampleWithIncludes("**.name"));
} }
public void testRecurseField1Exclusive() throws Exception { public void testRecurseField1Exclusive() throws Exception {
@ -831,7 +839,7 @@ public abstract class AbstractFilteringJsonGeneratorTestCase extends ESTestCase
.endObject() .endObject()
.endObject(); .endObject();
assertXContentBuilder(expected, sample("**.name", false)); assertXContentBuilder(expected, sampleWithExcludes("**.name"));
} }
public void testRecurseField2Inclusive() throws Exception { public void testRecurseField2Inclusive() throws Exception {
@ -875,7 +883,7 @@ public abstract class AbstractFilteringJsonGeneratorTestCase extends ESTestCase
.endObject() .endObject()
.endObject(); .endObject();
assertXContentBuilder(expected, sample("properties.**.name", true)); assertXContentBuilder(expected, sampleWithIncludes("properties.**.name"));
} }
public void testRecurseField2Exclusive() throws Exception { public void testRecurseField2Exclusive() throws Exception {
@ -940,10 +948,9 @@ public abstract class AbstractFilteringJsonGeneratorTestCase extends ESTestCase
.endObject() .endObject()
.endObject(); .endObject();
assertXContentBuilder(expected, sample("properties.**.name", false)); assertXContentBuilder(expected, sampleWithExcludes("properties.**.name"));
} }
public void testRecurseField3Inclusive() throws Exception { public void testRecurseField3Inclusive() throws Exception {
XContentBuilder expected = newXContentBuilder().startObject() XContentBuilder expected = newXContentBuilder().startObject()
.startObject("properties") .startObject("properties")
@ -970,7 +977,7 @@ public abstract class AbstractFilteringJsonGeneratorTestCase extends ESTestCase
.endObject() .endObject()
.endObject(); .endObject();
assertXContentBuilder(expected, sample("properties.*.en.**.name", true)); assertXContentBuilder(expected, sampleWithIncludes("properties.*.en.**.name"));
} }
public void testRecurseField3Exclusive() throws Exception { public void testRecurseField3Exclusive() throws Exception {
@ -1040,10 +1047,9 @@ public abstract class AbstractFilteringJsonGeneratorTestCase extends ESTestCase
.endObject() .endObject()
.endObject(); .endObject();
assertXContentBuilder(expected, sample("properties.*.en.**.name", false)); assertXContentBuilder(expected, sampleWithExcludes("properties.*.en.**.name"));
} }
public void testRecurseField4Inclusive() throws Exception { public void testRecurseField4Inclusive() throws Exception {
XContentBuilder expected = newXContentBuilder().startObject() XContentBuilder expected = newXContentBuilder().startObject()
.startObject("properties") .startObject("properties")
@ -1072,7 +1078,7 @@ public abstract class AbstractFilteringJsonGeneratorTestCase extends ESTestCase
.endObject() .endObject()
.endObject(); .endObject();
assertXContentBuilder(expected, sample("properties.**.distributors.name", true)); assertXContentBuilder(expected, sampleWithIncludes("properties.**.distributors.name"));
} }
public void testRecurseField4Exclusive() throws Exception { public void testRecurseField4Exclusive() throws Exception {
@ -1140,7 +1146,7 @@ public abstract class AbstractFilteringJsonGeneratorTestCase extends ESTestCase
.endObject() .endObject()
.endObject(); .endObject();
assertXContentBuilder(expected, sample("properties.**.distributors.name", false)); assertXContentBuilder(expected, sampleWithExcludes("properties.**.distributors.name"));
} }
public void testRawField() throws Exception { public void testRawField() throws Exception {
@ -1155,24 +1161,24 @@ public abstract class AbstractFilteringJsonGeneratorTestCase extends ESTestCase
// Test method: rawField(String fieldName, BytesReference content) // Test method: rawField(String fieldName, BytesReference content)
assertXContentBuilder(expectedRawField, newXContentBuilder().startObject().field("foo", 0).rawField("raw", raw).endObject()); assertXContentBuilder(expectedRawField, newXContentBuilder().startObject().field("foo", 0).rawField("raw", raw).endObject());
assertXContentBuilder(expectedRawFieldFiltered, assertXContentBuilder(expectedRawFieldFiltered,
newXContentBuilder("f*", true).startObject().field("foo", 0).rawField("raw", raw).endObject()); newXContentBuilderWithIncludes("f*").startObject().field("foo", 0).rawField("raw", raw).endObject());
assertXContentBuilder(expectedRawFieldFiltered, assertXContentBuilder(expectedRawFieldFiltered,
newXContentBuilder("r*", false).startObject().field("foo", 0).rawField("raw", raw).endObject()); newXContentBuilderWithExcludes("r*").startObject().field("foo", 0).rawField("raw", raw).endObject());
assertXContentBuilder(expectedRawFieldNotFiltered, assertXContentBuilder(expectedRawFieldNotFiltered,
newXContentBuilder("r*", true).startObject().field("foo", 0).rawField("raw", raw).endObject()); newXContentBuilderWithIncludes("r*").startObject().field("foo", 0).rawField("raw", raw).endObject());
assertXContentBuilder(expectedRawFieldNotFiltered, assertXContentBuilder(expectedRawFieldNotFiltered,
newXContentBuilder("f*", false).startObject().field("foo", 0).rawField("raw", raw).endObject()); newXContentBuilderWithExcludes("f*").startObject().field("foo", 0).rawField("raw", raw).endObject());
// Test method: rawField(String fieldName, InputStream content) // Test method: rawField(String fieldName, InputStream content)
assertXContentBuilder(expectedRawField, assertXContentBuilder(expectedRawField,
newXContentBuilder().startObject().field("foo", 0).rawField("raw", raw.streamInput()).endObject()); newXContentBuilder().startObject().field("foo", 0).rawField("raw", raw.streamInput()).endObject());
assertXContentBuilder(expectedRawFieldFiltered, newXContentBuilder("f*", true).startObject().field("foo", 0) assertXContentBuilder(expectedRawFieldFiltered, newXContentBuilderWithIncludes("f*").startObject().field("foo", 0)
.rawField("raw", raw.streamInput()).endObject()); .rawField("raw", raw.streamInput()).endObject());
assertXContentBuilder(expectedRawFieldFiltered, newXContentBuilder("r*", false).startObject().field("foo", 0) assertXContentBuilder(expectedRawFieldFiltered, newXContentBuilderWithExcludes("r*").startObject().field("foo", 0)
.rawField("raw", raw.streamInput()).endObject()); .rawField("raw", raw.streamInput()).endObject());
assertXContentBuilder(expectedRawFieldNotFiltered, newXContentBuilder("r*", true).startObject().field("foo", 0) assertXContentBuilder(expectedRawFieldNotFiltered, newXContentBuilderWithIncludes("r*").startObject().field("foo", 0)
.rawField("raw", raw.streamInput()).endObject()); .rawField("raw", raw.streamInput()).endObject());
assertXContentBuilder(expectedRawFieldNotFiltered, newXContentBuilder("f*", false).startObject().field("foo", 0) assertXContentBuilder(expectedRawFieldNotFiltered, newXContentBuilderWithExcludes("f*").startObject().field("foo", 0)
.rawField("raw", raw.streamInput()).endObject()); .rawField("raw", raw.streamInput()).endObject());
} }
@ -1180,48 +1186,209 @@ public abstract class AbstractFilteringJsonGeneratorTestCase extends ESTestCase
// Test: Array of values (no filtering) // Test: Array of values (no filtering)
XContentBuilder expected = newXContentBuilder().startObject().startArray("tags").value("lorem").value("ipsum").value("dolor") XContentBuilder expected = newXContentBuilder().startObject().startArray("tags").value("lorem").value("ipsum").value("dolor")
.endArray().endObject(); .endArray().endObject();
assertXContentBuilder(expected, newXContentBuilder("t*", true).startObject().startArray("tags").value("lorem").value("ipsum") assertXContentBuilder(expected, newXContentBuilderWithIncludes("t*").startObject().startArray("tags").value("lorem").value("ipsum")
.value("dolor").endArray().endObject()); .value("dolor").endArray().endObject());
assertXContentBuilder(expected, newXContentBuilder("tags", true).startObject().startArray("tags").value("lorem").value("ipsum") assertXContentBuilder(expected, newXContentBuilderWithIncludes("tags").startObject().startArray("tags").value("lorem")
.value("dolor").endArray().endObject()); .value("ipsum").value("dolor").endArray().endObject());
assertXContentBuilder(expected, newXContentBuilder("a", false).startObject().startArray("tags").value("lorem").value("ipsum") assertXContentBuilder(expected, newXContentBuilderWithExcludes("a").startObject().startArray("tags").value("lorem").value("ipsum")
.value("dolor").endArray().endObject()); .value("dolor").endArray().endObject());
// Test: Array of values (with filtering) // Test: Array of values (with filtering)
assertXContentBuilder(newXContentBuilder().startObject().endObject(), newXContentBuilder("foo", true).startObject() assertXContentBuilder(newXContentBuilder().startObject().endObject(), newXContentBuilderWithIncludes("foo").startObject()
.startArray("tags").value("lorem").value("ipsum").value("dolor").endArray().endObject()); .startArray("tags").value("lorem").value("ipsum").value("dolor").endArray().endObject());
assertXContentBuilder(newXContentBuilder().startObject().endObject(), newXContentBuilder("t*", false).startObject() assertXContentBuilder(newXContentBuilder().startObject().endObject(), newXContentBuilderWithExcludes("t*").startObject()
.startArray("tags").value("lorem").value("ipsum").value("dolor").endArray().endObject()); .startArray("tags").value("lorem").value("ipsum").value("dolor").endArray().endObject());
assertXContentBuilder(newXContentBuilder().startObject().endObject(), newXContentBuilder("tags", false).startObject() assertXContentBuilder(newXContentBuilder().startObject().endObject(), newXContentBuilderWithExcludes("tags").startObject()
.startArray("tags").value("lorem").value("ipsum").value("dolor").endArray().endObject()); .startArray("tags").value("lorem").value("ipsum").value("dolor").endArray().endObject());
// Test: Array of objects (no filtering) // Test: Array of objects (no filtering)
expected = newXContentBuilder().startObject().startArray("tags").startObject().field("lastname", "lorem").endObject().startObject() expected = newXContentBuilder().startObject().startArray("tags").startObject().field("lastname", "lorem").endObject().startObject()
.field("firstname", "ipsum").endObject().endArray().endObject(); .field("firstname", "ipsum").endObject().endArray().endObject();
assertXContentBuilder(expected, newXContentBuilder("t*", true).startObject().startArray("tags").startObject() assertXContentBuilder(expected, newXContentBuilderWithIncludes("t*").startObject().startArray("tags").startObject()
.field("lastname", "lorem").endObject().startObject().field("firstname", "ipsum").endObject().endArray().endObject()); .field("lastname", "lorem").endObject().startObject().field("firstname", "ipsum").endObject().endArray().endObject());
assertXContentBuilder(expected, newXContentBuilder("tags", true).startObject().startArray("tags").startObject() assertXContentBuilder(expected, newXContentBuilderWithIncludes("tags").startObject().startArray("tags").startObject()
.field("lastname", "lorem").endObject().startObject().field("firstname", "ipsum").endObject().endArray().endObject()); .field("lastname", "lorem").endObject().startObject().field("firstname", "ipsum").endObject().endArray().endObject());
assertXContentBuilder(expected, newXContentBuilder("a", false).startObject().startArray("tags").startObject() assertXContentBuilder(expected, newXContentBuilderWithExcludes("a").startObject().startArray("tags").startObject()
.field("lastname", "lorem").endObject().startObject().field("firstname", "ipsum").endObject().endArray().endObject()); .field("lastname", "lorem").endObject().startObject().field("firstname", "ipsum").endObject().endArray().endObject());
// Test: Array of objects (with filtering) // Test: Array of objects (with filtering)
assertXContentBuilder(newXContentBuilder().startObject().endObject(), assertXContentBuilder(newXContentBuilder().startObject().endObject(),
newXContentBuilder("foo", true).startObject().startArray("tags").startObject().field("lastname", "lorem").endObject() newXContentBuilderWithIncludes("foo").startObject().startArray("tags").startObject().field("lastname", "lorem").endObject()
.startObject().field("firstname", "ipsum").endObject().endArray().endObject()); .startObject().field("firstname", "ipsum").endObject().endArray().endObject());
assertXContentBuilder(newXContentBuilder().startObject().endObject(), assertXContentBuilder(newXContentBuilder().startObject().endObject(),
newXContentBuilder("t*", false).startObject().startArray("tags").startObject().field("lastname", "lorem").endObject() newXContentBuilderWithExcludes("t*").startObject().startArray("tags").startObject().field("lastname", "lorem").endObject()
.startObject().field("firstname", "ipsum").endObject().endArray().endObject()); .startObject().field("firstname", "ipsum").endObject().endArray().endObject());
assertXContentBuilder(newXContentBuilder().startObject().endObject(), assertXContentBuilder(newXContentBuilder().startObject().endObject(),
newXContentBuilder("tags", false).startObject().startArray("tags").startObject().field("lastname", "lorem").endObject() newXContentBuilderWithExcludes("tags").startObject().startArray("tags").startObject().field("lastname", "lorem").endObject()
.startObject().field("firstname", "ipsum").endObject().endArray().endObject()); .startObject().field("firstname", "ipsum").endObject().endArray().endObject());
// Test: Array of objects (with partial filtering) // Test: Array of objects (with partial filtering)
expected = newXContentBuilder().startObject().startArray("tags").startObject().field("firstname", "ipsum").endObject().endArray() expected = newXContentBuilder().startObject().startArray("tags").startObject().field("firstname", "ipsum").endObject().endArray()
.endObject(); .endObject();
assertXContentBuilder(expected, newXContentBuilder("t*.firstname", true).startObject().startArray("tags").startObject() assertXContentBuilder(expected, newXContentBuilderWithIncludes("t*.firstname").startObject().startArray("tags").startObject()
.field("lastname", "lorem").endObject().startObject().field("firstname", "ipsum").endObject().endArray().endObject()); .field("lastname", "lorem").endObject().startObject().field("firstname", "ipsum").endObject().endArray().endObject());
assertXContentBuilder(expected, newXContentBuilder("t*.lastname", false).startObject().startArray("tags").startObject() assertXContentBuilder(expected, newXContentBuilderWithExcludes("t*.lastname").startObject().startArray("tags").startObject()
.field("lastname", "lorem").endObject().startObject().field("firstname", "ipsum").endObject().endArray().endObject()); .field("lastname", "lorem").endObject().startObject().field("firstname", "ipsum").endObject().endArray().endObject());
} }
public void testEmptyObject() throws IOException {
final Function<XContentBuilder, XContentBuilder> build = builder -> {
try {
return builder.startObject().startObject("foo").endObject().endObject();
} catch (IOException e) {
throw new RuntimeException(e);
}
};
XContentBuilder expected = build.apply(newXContentBuilder());
assertXContentBuilder(expected, build.apply(newXContentBuilderWithIncludes("foo")));
assertXContentBuilder(expected, build.apply(newXContentBuilderWithExcludes("bar")));
assertXContentBuilder(expected, build.apply(newXContentBuilder(singleton("f*"), singleton("baz"))));
expected = newXContentBuilder().startObject().endObject();
assertXContentBuilder(expected, build.apply(newXContentBuilderWithExcludes("foo")));
assertXContentBuilder(expected, build.apply(newXContentBuilderWithIncludes("bar")));
assertXContentBuilder(expected, build.apply(newXContentBuilder(singleton("f*"), singleton("foo"))));
}
public void testSingleFieldObject() throws IOException {
final Function<XContentBuilder, XContentBuilder> build = builder -> {
try {
return builder.startObject().startObject("foo").field("bar", "test").endObject().endObject();
} catch (IOException e) {
throw new RuntimeException(e);
}
};
XContentBuilder expected = build.apply(newXContentBuilder());
assertXContentBuilder(expected, build.apply(newXContentBuilderWithIncludes("foo.bar")));
assertXContentBuilder(expected, build.apply(newXContentBuilderWithExcludes("foo.baz")));
assertXContentBuilder(expected, build.apply(newXContentBuilder(singleton("foo"), singleton("foo.baz"))));
expected = newXContentBuilder().startObject().endObject();
assertXContentBuilder(expected, build.apply(newXContentBuilderWithExcludes("foo.bar")));
assertXContentBuilder(expected, build.apply(newXContentBuilder(singleton("foo"), singleton("foo.b*"))));
}
public void testSingleFieldWithBothExcludesIncludes() throws IOException {
XContentBuilder expected = newXContentBuilder()
.startObject()
.field("pages", 456)
.field("price", 27.99)
.endObject();
assertXContentBuilder(expected, sampleWithFilters(singleton("p*"), singleton("properties")));
}
public void testObjectsInArrayWithBothExcludesIncludes() throws IOException {
Set<String> includes = Sets.newHashSet("tags", "authors");
Set<String> excludes = singleton("authors.name");
XContentBuilder expected = newXContentBuilder()
.startObject()
.startArray("tags")
.value("elasticsearch")
.value("java")
.endArray()
.startArray("authors")
.startObject()
.field("lastname", "John")
.field("firstname", "Doe")
.endObject()
.startObject()
.field("lastname", "William")
.field("firstname", "Smith")
.endObject()
.endArray()
.endObject();
assertXContentBuilder(expected, sampleWithFilters(includes, excludes));
}
public void testRecursiveObjectsInArrayWithBothExcludesIncludes() throws IOException {
Set<String> includes = Sets.newHashSet("**.language", "properties.weight");
Set<String> excludes = singleton("**.distributors");
XContentBuilder expected = newXContentBuilder()
.startObject()
.startObject("properties")
.field("weight", 0.8d)
.startObject("language")
.startObject("en")
.field("lang", "English")
.field("available", true)
.endObject()
.startObject("fr")
.field("lang", "French")
.field("available", false)
.endObject()
.endObject()
.endObject()
.endObject();
assertXContentBuilder(expected, sampleWithFilters(includes, excludes));
}
public void testRecursiveSameObjectWithBothExcludesIncludes() throws IOException {
Set<String> includes = singleton("**.distributors");
Set<String> excludes = singleton("**.distributors");
XContentBuilder expected = newXContentBuilder().startObject().endObject();
assertXContentBuilder(expected, sampleWithFilters(includes, excludes));
}
public void testRecursiveObjectsPropertiesWithBothExcludesIncludes() throws IOException {
Set<String> includes = singleton("**.en.*");
Set<String> excludes = Sets.newHashSet("**.distributors.*.name", "**.street");
XContentBuilder expected = newXContentBuilder()
.startObject()
.startObject("properties")
.startObject("language")
.startObject("en")
.field("lang", "English")
.field("available", true)
.startArray("distributors")
.startObject()
.field("name", "The Book Shop")
.startArray("addresses")
.startObject()
.field("city", "London")
.endObject()
.startObject()
.field("city", "Stornoway")
.endObject()
.endArray()
.endObject()
.startObject()
.field("name", "Sussex Books House")
.endObject()
.endArray()
.endObject()
.endObject()
.endObject()
.endObject();
assertXContentBuilder(expected, sampleWithFilters(includes, excludes));
}
public void testWithLfAtEnd() throws IOException {
final Function<XContentBuilder, XContentBuilder> build = builder -> {
try {
return builder.startObject().startObject("foo").field("bar", "baz").endObject().endObject().prettyPrint().lfAtEnd();
} catch (IOException e) {
throw new RuntimeException(e);
}
};
XContentBuilder expected = build.apply(newXContentBuilder());
assertXContentBuilder(expected, build.apply(newXContentBuilderWithIncludes("foo")));
assertXContentBuilder(expected, build.apply(newXContentBuilderWithExcludes("bar")));
assertXContentBuilder(expected, build.apply(newXContentBuilder(singleton("f*"), singleton("baz"))));
expected = newXContentBuilder().startObject().endObject().prettyPrint().lfAtEnd();
assertXContentBuilder(expected, build.apply(newXContentBuilderWithExcludes("foo")));
assertXContentBuilder(expected, build.apply(newXContentBuilderWithIncludes("bar")));
assertXContentBuilder(expected, build.apply(newXContentBuilder(singleton("f*"), singleton("foo"))));
}
} }

View File

@ -25,6 +25,8 @@ import com.fasterxml.jackson.core.filter.FilteringGeneratorDelegate;
import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import java.util.Collections;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
public class FilterPathGeneratorFilteringTests extends ESTestCase { public class FilterPathGeneratorFilteringTests extends ESTestCase {
@ -135,7 +137,7 @@ public class FilterPathGeneratorFilteringTests extends ESTestCase {
private void assertResult(String input, String filter, boolean inclusive, String expected) throws Exception { private void assertResult(String input, String filter, boolean inclusive, String expected) throws Exception {
try (BytesStreamOutput os = new BytesStreamOutput()) { try (BytesStreamOutput os = new BytesStreamOutput()) {
try (FilteringGeneratorDelegate generator = new FilteringGeneratorDelegate(JSON_FACTORY.createGenerator(os), try (FilteringGeneratorDelegate generator = new FilteringGeneratorDelegate(JSON_FACTORY.createGenerator(os),
new FilterPathBasedFilter(new String[] { filter }, inclusive), true, true)) { new FilterPathBasedFilter(Collections.singleton(filter), inclusive), true, true)) {
try (JsonParser parser = JSON_FACTORY.createParser(replaceQuotes(input))) { try (JsonParser parser = JSON_FACTORY.createParser(replaceQuotes(input))) {
while (parser.nextToken() != null) { while (parser.nextToken() != null) {
generator.copyCurrentStructure(parser); generator.copyCurrentStructure(parser);

View File

@ -19,8 +19,12 @@
package org.elasticsearch.common.xcontent.support.filtering; package org.elasticsearch.common.xcontent.support.filtering;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import java.util.Set;
import static java.util.Collections.singleton;
import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.arrayWithSize;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
@ -33,7 +37,7 @@ public class FilterPathTests extends ESTestCase {
public void testSimpleFilterPath() { public void testSimpleFilterPath() {
final String input = "test"; final String input = "test";
FilterPath[] filterPaths = FilterPath.compile(input); FilterPath[] filterPaths = FilterPath.compile(singleton(input));
assertNotNull(filterPaths); assertNotNull(filterPaths);
assertThat(filterPaths, arrayWithSize(1)); assertThat(filterPaths, arrayWithSize(1));
@ -52,7 +56,7 @@ public class FilterPathTests extends ESTestCase {
public void testFilterPathWithSubField() { public void testFilterPathWithSubField() {
final String input = "foo.bar"; final String input = "foo.bar";
FilterPath[] filterPaths = FilterPath.compile(input); FilterPath[] filterPaths = FilterPath.compile(singleton(input));
assertNotNull(filterPaths); assertNotNull(filterPaths);
assertThat(filterPaths, arrayWithSize(1)); assertThat(filterPaths, arrayWithSize(1));
@ -76,7 +80,7 @@ public class FilterPathTests extends ESTestCase {
public void testFilterPathWithSubFields() { public void testFilterPathWithSubFields() {
final String input = "foo.bar.quz"; final String input = "foo.bar.quz";
FilterPath[] filterPaths = FilterPath.compile(input); FilterPath[] filterPaths = FilterPath.compile(singleton(input));
assertNotNull(filterPaths); assertNotNull(filterPaths);
assertThat(filterPaths, arrayWithSize(1)); assertThat(filterPaths, arrayWithSize(1));
@ -103,13 +107,13 @@ public class FilterPathTests extends ESTestCase {
} }
public void testEmptyFilterPath() { public void testEmptyFilterPath() {
FilterPath[] filterPaths = FilterPath.compile(""); FilterPath[] filterPaths = FilterPath.compile(singleton(""));
assertNotNull(filterPaths); assertNotNull(filterPaths);
assertThat(filterPaths, arrayWithSize(0)); assertThat(filterPaths, arrayWithSize(0));
} }
public void testNullFilterPath() { public void testNullFilterPath() {
FilterPath[] filterPaths = FilterPath.compile((String) null); FilterPath[] filterPaths = FilterPath.compile(singleton(null));
assertNotNull(filterPaths); assertNotNull(filterPaths);
assertThat(filterPaths, arrayWithSize(0)); assertThat(filterPaths, arrayWithSize(0));
} }
@ -117,7 +121,7 @@ public class FilterPathTests extends ESTestCase {
public void testFilterPathWithEscapedDots() { public void testFilterPathWithEscapedDots() {
String input = "w.0.0.t"; String input = "w.0.0.t";
FilterPath[] filterPaths = FilterPath.compile(input); FilterPath[] filterPaths = FilterPath.compile(singleton(input));
assertNotNull(filterPaths); assertNotNull(filterPaths);
assertThat(filterPaths, arrayWithSize(1)); assertThat(filterPaths, arrayWithSize(1));
@ -149,7 +153,7 @@ public class FilterPathTests extends ESTestCase {
input = "w\\.0\\.0\\.t"; input = "w\\.0\\.0\\.t";
filterPaths = FilterPath.compile(input); filterPaths = FilterPath.compile(singleton(input));
assertNotNull(filterPaths); assertNotNull(filterPaths);
assertThat(filterPaths, arrayWithSize(1)); assertThat(filterPaths, arrayWithSize(1));
@ -167,7 +171,7 @@ public class FilterPathTests extends ESTestCase {
input = "w\\.0.0\\.t"; input = "w\\.0.0\\.t";
filterPaths = FilterPath.compile(input); filterPaths = FilterPath.compile(singleton(input));
assertNotNull(filterPaths); assertNotNull(filterPaths);
assertThat(filterPaths, arrayWithSize(1)); assertThat(filterPaths, arrayWithSize(1));
@ -188,7 +192,7 @@ public class FilterPathTests extends ESTestCase {
} }
public void testSimpleWildcardFilterPath() { public void testSimpleWildcardFilterPath() {
FilterPath[] filterPaths = FilterPath.compile("*"); FilterPath[] filterPaths = FilterPath.compile(singleton("*"));
assertNotNull(filterPaths); assertNotNull(filterPaths);
assertThat(filterPaths, arrayWithSize(1)); assertThat(filterPaths, arrayWithSize(1));
@ -206,7 +210,7 @@ public class FilterPathTests extends ESTestCase {
public void testWildcardInNameFilterPath() { public void testWildcardInNameFilterPath() {
String input = "f*o.bar"; String input = "f*o.bar";
FilterPath[] filterPaths = FilterPath.compile(input); FilterPath[] filterPaths = FilterPath.compile(singleton(input));
assertNotNull(filterPaths); assertNotNull(filterPaths);
assertThat(filterPaths, arrayWithSize(1)); assertThat(filterPaths, arrayWithSize(1));
@ -232,7 +236,7 @@ public class FilterPathTests extends ESTestCase {
} }
public void testDoubleWildcardFilterPath() { public void testDoubleWildcardFilterPath() {
FilterPath[] filterPaths = FilterPath.compile("**"); FilterPath[] filterPaths = FilterPath.compile(singleton("**"));
assertNotNull(filterPaths); assertNotNull(filterPaths);
assertThat(filterPaths, arrayWithSize(1)); assertThat(filterPaths, arrayWithSize(1));
@ -250,7 +254,7 @@ public class FilterPathTests extends ESTestCase {
public void testStartsWithDoubleWildcardFilterPath() { public void testStartsWithDoubleWildcardFilterPath() {
String input = "**.bar"; String input = "**.bar";
FilterPath[] filterPaths = FilterPath.compile(input); FilterPath[] filterPaths = FilterPath.compile(singleton(input));
assertNotNull(filterPaths); assertNotNull(filterPaths);
assertThat(filterPaths, arrayWithSize(1)); assertThat(filterPaths, arrayWithSize(1));
@ -274,7 +278,7 @@ public class FilterPathTests extends ESTestCase {
public void testContainsDoubleWildcardFilterPath() { public void testContainsDoubleWildcardFilterPath() {
String input = "foo.**.bar"; String input = "foo.**.bar";
FilterPath[] filterPaths = FilterPath.compile(input); FilterPath[] filterPaths = FilterPath.compile(singleton(input));
assertNotNull(filterPaths); assertNotNull(filterPaths);
assertThat(filterPaths, arrayWithSize(1)); assertThat(filterPaths, arrayWithSize(1));
@ -302,7 +306,7 @@ public class FilterPathTests extends ESTestCase {
} }
public void testMultipleFilterPaths() { public void testMultipleFilterPaths() {
String[] inputs = {"foo.**.bar.*", "test.dot\\.ted"}; Set<String> inputs = Sets.newHashSet("foo.**.bar.*", "test.dot\\.ted");
FilterPath[] filterPaths = FilterPath.compile(inputs); FilterPath[] filterPaths = FilterPath.compile(inputs);
assertNotNull(filterPaths); assertNotNull(filterPaths);

View File

@ -276,6 +276,41 @@ curl 'localhost:9200/_segments?pretty&filter_path=indices.**.version'
} }
-------------------------------------------------- --------------------------------------------------
It is also possible to exclude one or more fields by prefixing the filter with the char `-`:
[source,sh]
--------------------------------------------------
curl -XGET 'localhost:9200/_count?filter_path=-_shards'
{
"count" : 1
}
%
--------------------------------------------------
And for more control, both inclusive and exclusive filters can be combined in the same expression. In
this case, the exclusive filters will be applied first and the result will be filtered again using the
inclusive filters:
[source,sh]
--------------------------------------------------
curl -XGET 'localhost:9200/_cluster/state?filter_path=metadata.indices.*.state,-metadata.indices.logs-*'
{
"metadata" : {
"indices" : {
"index-1" : {
"state" : "open"
},
"index-3" : {
"state" : "open"
},
"index-2" : {
"state" : "open"
}
}
}
}%
--------------------------------------------------
Note that elasticsearch sometimes returns directly the raw value of a field, Note that elasticsearch sometimes returns directly the raw value of a field,
like the `_source` field. If you want to filter `_source` fields, you should like the `_source` field. If you want to filter `_source` fields, you should
consider combining the already existing `_source` parameter (see consider combining the already existing `_source` parameter (see

View File

@ -152,3 +152,49 @@
- is_false: nodes.$master.fs.data.0.path - is_false: nodes.$master.fs.data.0.path
- is_true: nodes.$master.fs.data.0.type - is_true: nodes.$master.fs.data.0.type
- is_true: nodes.$master.fs.data.0.total_in_bytes - is_true: nodes.$master.fs.data.0.total_in_bytes
---
"Nodes Stats filtered using both includes and excludes filters":
- do:
cluster.state: {}
# Get master node id
- set: { master_node: master }
# Nodes Stats with "nodes" field but no JVM stats
- do:
nodes.stats:
filter_path: [ "nodes", "-nodes.*.jvm", "-nodes.*.indices" ]
- is_false: cluster_name
- is_true: nodes
- is_true: nodes.$master.name
- is_true: nodes.$master.os
- is_false: nodes.$master.indices
- is_false: nodes.$master.jvm
# Nodes Stats with "nodes.*.indices" field and sub-fields but no indices segments
- do:
nodes.stats:
filter_path: "nodes.*.indices,-nodes.*.indices.segments"
- is_false: cluster_name
- is_true: nodes
- is_false: nodes.$master.name
- is_true: nodes.$master.indices
- is_true: nodes.$master.indices.docs
- is_false: nodes.$master.indices.segments
# Nodes Stats with "nodes.*.fs.data.t*" fields but no "type" field
- do:
nodes.stats:
filter_path: "nodes.*.fs.data.t*,-**.type"
- is_false: cluster_name
- is_true: nodes
- is_false: nodes.$master.name
- is_false: nodes.$master.indices
- is_false: nodes.$master.jvm
- is_true: nodes.$master.fs.data
- is_false: nodes.$master.fs.data.0.type
- is_true: nodes.$master.fs.data.0.total_in_bytes

View File

@ -84,3 +84,64 @@
- is_true: hits.hits.1._index - is_true: hits.hits.1._index
- is_false: hits.hits.1._type - is_false: hits.hits.1._type
- is_true: hits.hits.1._id - is_true: hits.hits.1._id
---
"Search results filtered using both includes and excludes filters":
- do:
bulk:
refresh: true
body: |
{"index": {"_index": "index-1", "_type": "type-1", "_id": "1"}}
{"name": "First document", "properties": {"size": 123, "meta": {"foo": "bar"}}}
{"index": {"_index": "index-1", "_type": "type-1", "_id": "2"}}
{"name": "Second document", "properties": {"size": 465, "meta": {"foo": "bar", "baz": "qux"}}}
- do:
search:
filter_path: [ "-**._source.properties", "**._source" ]
body: { query: { match_all: {} } }
- is_false: took
- is_true: hits.hits.0._source
- is_true: hits.hits.0._source.name
- is_false: hits.hits.0._source.properties
- is_true: hits.hits.1._source
- is_true: hits.hits.1._source.name
- is_false: hits.hits.1._source.properties
- do:
search:
filter_path: [ "**.properties" , "-hits.hits._source.properties.meta" ]
body: { query: { match_all: {} } }
- is_false: took
- is_true: hits.hits.0._source
- is_false: hits.hits.0._source.name
- is_true: hits.hits.0._source.properties
- is_true: hits.hits.0._source.properties.size
- is_false: hits.hits.0._source.properties.meta
- is_true: hits.hits.1._source
- is_false: hits.hits.1._source.name
- is_true: hits.hits.1._source.properties
- is_true: hits.hits.1._source.properties.size
- is_false: hits.hits.1._source.properties.meta
- do:
search:
filter_path: "**._source,-**.meta.foo"
body: { query: { match_all: {} } }
- is_false: took
- is_true: hits.hits.0._source
- is_true: hits.hits.0._source.name
- is_true: hits.hits.0._source.properties
- is_true: hits.hits.0._source.properties.size
- is_false: hits.hits.0._source.properties.meta.foo
- do:
count:
filter_path: "-*"
body: { query: { match_all: {} } }
- is_false: count
- is_false: _shards