/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.search.fetch.subphase;
import org.elasticsearch.common.Booleans;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.support.XContentMapValues;
import org.elasticsearch.rest.RestRequest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
/**
* Context used to fetch the {@code _source}.
*/
public class FetchSourceContext implements Writeable, ToXContent {
public static final ParseField INCLUDES_FIELD = new ParseField("includes", "include");
public static final ParseField EXCLUDES_FIELD = new ParseField("excludes", "exclude");
public static final FetchSourceContext FETCH_SOURCE = new FetchSourceContext(true);
public static final FetchSourceContext DO_NOT_FETCH_SOURCE = new FetchSourceContext(false);
private final boolean fetchSource;
private final String[] includes;
private final String[] excludes;
private Function<Map<String, ?>, Map<String, Object>> filter;
public FetchSourceContext(boolean fetchSource, String[] includes, String[] excludes) {
this.fetchSource = fetchSource;
this.includes = includes == null ? Strings.EMPTY_ARRAY : includes;
this.excludes = excludes == null ? Strings.EMPTY_ARRAY : excludes;
}
public FetchSourceContext(boolean fetchSource) {
this(fetchSource, Strings.EMPTY_ARRAY, Strings.EMPTY_ARRAY);
}
public FetchSourceContext(StreamInput in) throws IOException {
fetchSource = in.readBoolean();
includes = in.readStringArray();
excludes = in.readStringArray();
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeBoolean(fetchSource);
out.writeStringArray(includes);
out.writeStringArray(excludes);
}
public boolean fetchSource() {
return this.fetchSource;
}
public String[] includes() {
return this.includes;
}
public String[] excludes() {
return this.excludes;
}
public static FetchSourceContext parseFromRestRequest(RestRequest request) {
Boolean fetchSource = null;
String[] source_excludes = null;
String[] source_includes = null;
String source = request.param("_source");
if (source != null) {
if (Booleans.isTrue(source)) {
fetchSource = true;
} else if (Booleans.isFalse(source)) {
fetchSource = false;
} else {
source_includes = Strings.splitStringByCommaToArray(source);
}
}
String sIncludes = request.param("_source_includes");
sIncludes = request.param("_source_include", sIncludes);
if (sIncludes != null) {
source_includes = Strings.splitStringByCommaToArray(sIncludes);
}
String sExcludes = request.param("_source_excludes");
sExcludes = request.param("_source_exclude", sExcludes);
if (sExcludes != null) {
source_excludes = Strings.splitStringByCommaToArray(sExcludes);
}
if (fetchSource != null || source_includes != null || source_excludes != null) {
return new FetchSourceContext(fetchSource == null ? true : fetchSource, source_includes, source_excludes);
}
return null;
}
public static FetchSourceContext fromXContent(XContentParser parser) throws IOException {
XContentParser.Token token = parser.currentToken();
boolean fetchSource = true;
String[] includes = Strings.EMPTY_ARRAY;
String[] excludes = Strings.EMPTY_ARRAY;
if (token == XContentParser.Token.VALUE_BOOLEAN) {
fetchSource = parser.booleanValue();
} else if (token == XContentParser.Token.VALUE_STRING) {
includes = new String[]{parser.text()};
} else if (token == XContentParser.Token.START_ARRAY) {
ArrayList<String> list = new ArrayList<>();
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
list.add(parser.text());
}
includes = list.toArray(new String[list.size()]);
} else if (token == XContentParser.Token.START_OBJECT) {
String currentFieldName = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_ARRAY) {
if (INCLUDES_FIELD.match(currentFieldName)) {
List<String> includesList = new ArrayList<>();
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
if (token == XContentParser.Token.VALUE_STRING) {
includesList.add(parser.text());
} else {
throw new ParsingException(parser.getTokenLocation(), "Unknown key for a " + token
+ " in [" + currentFieldName + "].", parser.getTokenLocation());
}
}
includes = includesList.toArray(new String[includesList.size()]);
} else if (EXCLUDES_FIELD.match(currentFieldName)) {
List<String> excludesList = new ArrayList<>();
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
if (token == XContentParser.Token.VALUE_STRING) {
excludesList.add(parser.text());
} else {
throw new ParsingException(parser.getTokenLocation(), "Unknown key for a " + token
+ " in [" + currentFieldName + "].", parser.getTokenLocation());
}
}
excludes = excludesList.toArray(new String[excludesList.size()]);
} else {
throw new ParsingException(parser.getTokenLocation(), "Unknown key for a " + token
+ " in [" + currentFieldName + "].", parser.getTokenLocation());
}
} else if (token == XContentParser.Token.VALUE_STRING) {
if (INCLUDES_FIELD.match(currentFieldName)) {
includes = new String[] {parser.text()};
} else if (EXCLUDES_FIELD.match(currentFieldName)) {
excludes = new String[] {parser.text()};
} else {
throw new ParsingException(parser.getTokenLocation(), "Unknown key for a " + token
+ " in [" + currentFieldName + "].", parser.getTokenLocation());
}
} else {
throw new ParsingException(parser.getTokenLocation(), "Unknown key for a " + token + " in [" + currentFieldName + "].",
parser.getTokenLocation());
}
}
} else {
throw new ParsingException(parser.getTokenLocation(), "Expected one of [" + XContentParser.Token.VALUE_BOOLEAN + ", "
+ XContentParser.Token.START_OBJECT + "] but found [" + token + "]", parser.getTokenLocation());
}
return new FetchSourceContext(fetchSource, includes, excludes);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
if (fetchSource) {
builder.startObject();
builder.array(INCLUDES_FIELD.getPreferredName(), includes);
builder.array(EXCLUDES_FIELD.getPreferredName(), excludes);
builder.endObject();
} else {
builder.value(false);
}
return builder;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FetchSourceContext that = (FetchSourceContext) o;
if (fetchSource != that.fetchSource) return false;
if (!Arrays.equals(excludes, that.excludes)) return false;
if (!Arrays.equals(includes, that.includes)) return false;
return true;
}
@Override
public int hashCode() {
int result = (fetchSource ? 1 : 0);
result = 31 * result + (includes != null ? Arrays.hashCode(includes) : 0);
result = 31 * result + (excludes != null ? Arrays.hashCode(excludes) : 0);
return result;
}
/**
* Returns a filter function that expects the source map as an input and returns
* the filtered map.
*/
public Function<Map<String, ?>, Map<String, Object>> getFilter() {
if (filter == null) {
filter = XContentMapValues.filter(includes, excludes);
}
return filter;
}
}