Merge remote-tracking branch 'origin/master' into feature/client_aggs_parsing

This commit is contained in:
Tanguy Leroux 2017-04-21 15:46:18 +02:00
commit adef5e227a
7 changed files with 119 additions and 22 deletions

View File

@ -91,7 +91,13 @@ public class PrioritizedEsThreadPoolExecutor extends EsThreadPoolExecutor {
for (Runnable runnable : runnables) { for (Runnable runnable : runnables) {
if (runnable instanceof TieBreakingPrioritizedRunnable) { if (runnable instanceof TieBreakingPrioritizedRunnable) {
TieBreakingPrioritizedRunnable t = (TieBreakingPrioritizedRunnable) runnable; TieBreakingPrioritizedRunnable t = (TieBreakingPrioritizedRunnable) runnable;
pending.add(new Pending(unwrap(t.runnable), t.priority(), t.insertionOrder, executing)); Runnable innerRunnable = t.runnable;
if (innerRunnable != null) {
/** innerRunnable can be null if task is finished but not removed from executor yet,
* see {@link TieBreakingPrioritizedRunnable#run} and {@link TieBreakingPrioritizedRunnable#runAndClean}
*/
pending.add(new Pending(unwrap(innerRunnable), t.priority(), t.insertionOrder, executing));
}
} else if (runnable instanceof PrioritizedFutureTask) { } else if (runnable instanceof PrioritizedFutureTask) {
PrioritizedFutureTask t = (PrioritizedFutureTask) runnable; PrioritizedFutureTask t = (PrioritizedFutureTask) runnable;
Object task = t.task; Object task = t.task;

View File

@ -20,8 +20,10 @@
package org.elasticsearch.common.xcontent; package org.elasticsearch.common.xcontent;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.xcontent.XContentParser.Token; import org.elasticsearch.common.xcontent.XContentParser.Token;
import org.elasticsearch.rest.action.search.RestSearchAction;
import java.io.IOException; import java.io.IOException;
import java.util.Locale; import java.util.Locale;
@ -107,4 +109,37 @@ public final class XContentParserUtils {
} }
return value; return value;
} }
/**
* This method expects that the current token is a {@code XContentParser.Token.FIELD_NAME} and
* that the current field name is the concatenation of a type, delimiter and name (ex: terms#foo
* where "terms" refers to the type of a registered {@link NamedXContentRegistry.Entry}, "#" is
* the delimiter and "foo" the name of the object to parse).
*
* The method splits the field's name to extract the type and name and then parses the object
* using the {@link XContentParser#namedObject(Class, String, Object)} method.
*
* @param parser the current {@link XContentParser}
* @param delimiter the delimiter to use to splits the field's name
* @param objectClass the object class of the object to parse
* @param <T> the type of the object to parse
* @return the parsed object
* @throws IOException if anything went wrong during parsing or if the type or name cannot be derived
* from the field's name
*/
public static <T> T parseTypedKeysObject(XContentParser parser, String delimiter, Class<T> objectClass) throws IOException {
ensureExpectedToken(XContentParser.Token.FIELD_NAME, parser.currentToken(), parser::getTokenLocation);
String currentFieldName = parser.currentName();
if (Strings.hasLength(currentFieldName)) {
int position = currentFieldName.indexOf(delimiter);
if (position > 0) {
String type = currentFieldName.substring(0, position);
String name = currentFieldName.substring(position + 1);
return parser.namedObject(objectClass, type, name);
}
}
throw new ParsingException(parser.getTokenLocation(), "Cannot parse object of class [" + objectClass.getSimpleName()
+ "] without type information. Set [" + RestSearchAction.TYPED_KEYS_PARAM + "] parameter on the request to ensure the"
+ " type information is added to the response output");
}
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.search.aggregations; package org.elasticsearch.search.aggregations;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation; import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator;
@ -81,7 +82,7 @@ public abstract class InternalMultiBucketAggregation<A extends InternalMultiBuck
} }
} }
public abstract static class InternalBucket implements Bucket { public abstract static class InternalBucket implements Bucket, Writeable {
public Object getProperty(String containingAggName, List<String> path) { public Object getProperty(String containingAggName, List<String> path) {
if (path.isEmpty()) { if (path.isEmpty()) {

View File

@ -19,7 +19,6 @@
package org.elasticsearch.search.aggregations.bucket; package org.elasticsearch.search.aggregations.bucket;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.util.Comparators; import org.elasticsearch.common.util.Comparators;
import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.search.aggregations.Aggregation; import org.elasticsearch.search.aggregations.Aggregation;
@ -37,7 +36,7 @@ public interface MultiBucketsAggregation extends Aggregation {
* A bucket represents a criteria to which all documents that fall in it adhere to. It is also uniquely identified * A bucket represents a criteria to which all documents that fall in it adhere to. It is also uniquely identified
* by a key, and can potentially hold sub-aggregations computed over all documents in it. * by a key, and can potentially hold sub-aggregations computed over all documents in it.
*/ */
interface Bucket extends HasAggregations, ToXContent, Writeable { interface Bucket extends HasAggregations, ToXContent {
/** /**
* @return The key associated with the bucket * @return The key associated with the bucket
*/ */

View File

@ -21,7 +21,6 @@ package org.elasticsearch.search.suggest;
import org.apache.lucene.util.CollectionUtil; import org.apache.lucene.util.CollectionUtil;
import org.elasticsearch.common.CheckedFunction; import org.elasticsearch.common.CheckedFunction;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Streamable; import org.elasticsearch.common.io.stream.Streamable;
@ -33,6 +32,7 @@ import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentParserUtils;
import org.elasticsearch.rest.action.search.RestSearchAction; import org.elasticsearch.rest.action.search.RestSearchAction;
import org.elasticsearch.search.aggregations.Aggregation; import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry; import org.elasticsearch.search.suggest.Suggest.Suggestion.Entry;
@ -386,22 +386,7 @@ public class Suggest implements Iterable<Suggest.Suggestion<? extends Entry<? ex
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Suggestion<? extends Entry<? extends Option>> fromXContent(XContentParser parser) throws IOException { public static Suggestion<? extends Entry<? extends Option>> fromXContent(XContentParser parser) throws IOException {
ensureExpectedToken(XContentParser.Token.FIELD_NAME, parser.currentToken(), parser::getTokenLocation); return XContentParserUtils.parseTypedKeysObject(parser, Aggregation.TYPED_KEYS_DELIMITER, Suggestion.class);
String typeAndName = parser.currentName();
// we need to extract the type prefix from the name and throw error if it is not present
int delimiterPos = typeAndName.indexOf(Aggregation.TYPED_KEYS_DELIMITER);
String type;
String name;
if (delimiterPos > 0) {
type = typeAndName.substring(0, delimiterPos);
name = typeAndName.substring(delimiterPos + 1);
} else {
throw new ParsingException(parser.getTokenLocation(),
"Cannot parse suggestion response without type information. Set [" + RestSearchAction.TYPED_KEYS_PARAM
+ "] parameter on the request to ensure the type information is added to the response output");
}
return parser.namedObject(Suggestion.class, type, name);
} }
protected static <E extends Suggestion.Entry<?>> void parseEntries(XContentParser parser, Suggestion<E> suggestion, protected static <E extends Suggestion.Entry<?>> void parseEntries(XContentParser parser, Suggestion<E> suggestion,

View File

@ -19,15 +19,22 @@
package org.elasticsearch.common.xcontent; package org.elasticsearch.common.xcontent;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static org.elasticsearch.common.xcontent.XContentHelper.toXContent;
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
import static org.elasticsearch.common.xcontent.XContentParserUtils.parseTypedKeysObject;
public class XContentParserUtilsTests extends ESTestCase { public class XContentParserUtilsTests extends ESTestCase {
public void testEnsureExpectedToken() throws IOException { public void testEnsureExpectedToken() throws IOException {
final XContentParser.Token randomToken = randomFrom(XContentParser.Token.values()); final XContentParser.Token randomToken = randomFrom(XContentParser.Token.values());
try (XContentParser parser = createParser(JsonXContent.jsonXContent, "{}")) { try (XContentParser parser = createParser(JsonXContent.jsonXContent, "{}")) {
@ -40,4 +47,68 @@ public class XContentParserUtilsTests extends ESTestCase {
ensureExpectedToken(XContentParser.Token.END_OBJECT, parser.nextToken(), parser::getTokenLocation); ensureExpectedToken(XContentParser.Token.END_OBJECT, parser.nextToken(), parser::getTokenLocation);
} }
} }
public void testParseTypedKeysObject() throws IOException {
final String delimiter = randomFrom("#", ":", "/", "-", "_", "|", "_delim_");
final XContentType xContentType = randomFrom(XContentType.values());
List<NamedXContentRegistry.Entry> namedXContents = new ArrayList<>();
namedXContents.add(new NamedXContentRegistry.Entry(Boolean.class, new ParseField("bool"), parser -> {
ensureExpectedToken(XContentParser.Token.VALUE_BOOLEAN, parser.nextToken(), parser::getTokenLocation);
return parser.booleanValue();
}));
namedXContents.add(new NamedXContentRegistry.Entry(Long.class, new ParseField("long"), parser -> {
ensureExpectedToken(XContentParser.Token.VALUE_NUMBER, parser.nextToken(), parser::getTokenLocation);
return parser.longValue();
}));
final NamedXContentRegistry namedXContentRegistry = new NamedXContentRegistry(namedXContents);
BytesReference bytes = toXContent((builder, params) -> builder.field("test", 0), xContentType, randomBoolean());
try (XContentParser parser = xContentType.xContent().createParser(namedXContentRegistry, bytes)) {
parser.nextToken();
ParsingException e = expectThrows(ParsingException.class, () -> parseTypedKeysObject(parser, delimiter, Boolean.class));
assertEquals("Failed to parse object: expecting token of type [FIELD_NAME] but found [START_OBJECT]", e.getMessage());
parser.nextToken();
e = expectThrows(ParsingException.class, () -> parseTypedKeysObject(parser, delimiter, Boolean.class));
assertEquals("Cannot parse object of class [Boolean] without type information. Set [typed_keys] parameter " +
"on the request to ensure the type information is added to the response output", e.getMessage());
}
bytes = toXContent((builder, params) -> builder.field("type" + delimiter + "name", 0), xContentType, randomBoolean());
try (XContentParser parser = xContentType.xContent().createParser(namedXContentRegistry, bytes)) {
ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation);
ensureExpectedToken(XContentParser.Token.FIELD_NAME, parser.nextToken(), parser::getTokenLocation);
NamedXContentRegistry.UnknownNamedObjectException e = expectThrows(NamedXContentRegistry.UnknownNamedObjectException.class,
() -> parseTypedKeysObject(parser, delimiter, Boolean.class));
assertEquals("Unknown Boolean [type]", e.getMessage());
assertEquals("type", e.getName());
assertEquals("java.lang.Boolean", e.getCategoryClass());
}
final long longValue = randomLong();
final boolean boolValue = randomBoolean();
bytes = toXContent((builder, params) -> {
builder.field("long" + delimiter + "l", longValue);
builder.field("bool" + delimiter + "b", boolValue);
return builder;
}, xContentType, randomBoolean());
try (XContentParser parser = xContentType.xContent().createParser(namedXContentRegistry, bytes)) {
ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation);
ensureExpectedToken(XContentParser.Token.FIELD_NAME, parser.nextToken(), parser::getTokenLocation);
Long parsedLong = parseTypedKeysObject(parser, delimiter, Long.class);
assertNotNull(parsedLong);
assertEquals(longValue, parsedLong.longValue());
ensureExpectedToken(XContentParser.Token.FIELD_NAME, parser.nextToken(), parser::getTokenLocation);
Boolean parsedBoolean = parseTypedKeysObject(parser, delimiter, Boolean.class);
assertNotNull(parsedBoolean);
assertEquals(boolValue, parsedBoolean);
ensureExpectedToken(XContentParser.Token.END_OBJECT, parser.nextToken(), parser::getTokenLocation);
}
}
} }

View File

@ -133,7 +133,7 @@ public class SuggestionTests extends ESTestCase {
ensureExpectedToken(XContentParser.Token.FIELD_NAME, parser.nextToken(), parser::getTokenLocation); ensureExpectedToken(XContentParser.Token.FIELD_NAME, parser.nextToken(), parser::getTokenLocation);
ParsingException e = expectThrows(ParsingException.class, () -> Suggestion.fromXContent(parser)); ParsingException e = expectThrows(ParsingException.class, () -> Suggestion.fromXContent(parser));
assertEquals( assertEquals(
"Cannot parse suggestion response without type information. " "Cannot parse object of class [Suggestion] without type information. "
+ "Set [typed_keys] parameter on the request to ensure the type information " + "Set [typed_keys] parameter on the request to ensure the type information "
+ "is added to the response output", e.getMessage()); + "is added to the response output", e.getMessage());
} }