/* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.elasticsearch.index.reindex.remote; import org.elasticsearch.Version; import org.elasticsearch.index.reindex.ScrollableHitSource.BasicHit; import org.elasticsearch.index.reindex.ScrollableHitSource.Hit; import org.elasticsearch.index.reindex.ScrollableHitSource.Response; import org.elasticsearch.index.reindex.ScrollableHitSource.SearchFailure; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException; import org.elasticsearch.common.xcontent.ConstructingObjectParser; import org.elasticsearch.common.xcontent.ObjectParser; import org.elasticsearch.common.xcontent.ObjectParser.ValueType; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentLocation; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; import java.io.IOException; import java.util.List; import java.util.function.BiFunction; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static java.util.Objects.requireNonNull; import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; /** * Parsers to convert the response from the remote host into objects useful for {@link RemoteScrollableHitSource}. */ final class RemoteResponseParsers { private RemoteResponseParsers() {} /** * Parser for an individual {@code hit} element. */ public static final ConstructingObjectParser<BasicHit, XContentType> HIT_PARSER = new ConstructingObjectParser<>("hit", true, a -> { int i = 0; String index = (String) a[i++]; String type = (String) a[i++]; String id = (String) a[i++]; Long version = (Long) a[i++]; return new BasicHit(index, type, id, version == null ? -1 : version); }); static { HIT_PARSER.declareString(constructorArg(), new ParseField("_index")); HIT_PARSER.declareString(constructorArg(), new ParseField("_type")); HIT_PARSER.declareString(constructorArg(), new ParseField("_id")); HIT_PARSER.declareLong(optionalConstructorArg(), new ParseField("_version")); HIT_PARSER.declareObject(((basicHit, tuple) -> basicHit.setSource(tuple.v1(), tuple.v2())), (p, s) -> { try { /* * We spool the data from the remote back into xcontent so we can get bytes to send. There ought to be a better way but for * now this should do. */ try (XContentBuilder b = XContentBuilder.builder(s.xContent())) { b.copyCurrentStructure(p); // a hack but this lets us get the right xcontent type to go with the source return new Tuple<>(b.bytes(), s); } } catch (IOException e) { throw new ParsingException(p.getTokenLocation(), "[hit] failed to parse [_source]", e); } }, new ParseField("_source")); ParseField routingField = new ParseField("_routing"); ParseField parentField = new ParseField("_parent"); ParseField ttlField = new ParseField("_ttl"); HIT_PARSER.declareString(BasicHit::setRouting, routingField); HIT_PARSER.declareString(BasicHit::setParent, parentField); // Pre-2.0.0 parent and routing come back in "fields" class Fields { String routing; String parent; } ObjectParser<Fields, XContentType> fieldsParser = new ObjectParser<>("fields", Fields::new); HIT_PARSER.declareObject((hit, fields) -> { hit.setRouting(fields.routing); hit.setParent(fields.parent); }, fieldsParser, new ParseField("fields")); fieldsParser.declareString((fields, routing) -> fields.routing = routing, routingField); fieldsParser.declareString((fields, parent) -> fields.parent = parent, parentField); fieldsParser.declareLong((fields, ttl) -> {}, ttlField); // ignore ttls since they have been removed } /** * Parser for the {@code hits} element. Parsed to an array of {@code [total (Long), hits (List<Hit>)]}. */ public static final ConstructingObjectParser<Object[], XContentType> HITS_PARSER = new ConstructingObjectParser<>("hits", true, a -> a); static { HITS_PARSER.declareLong(constructorArg(), new ParseField("total")); HITS_PARSER.declareObjectArray(constructorArg(), HIT_PARSER, new ParseField("hits")); } /** * Parser for {@code failed} shards in the {@code _shards} elements. */ public static final ConstructingObjectParser<SearchFailure, XContentType> SEARCH_FAILURE_PARSER = new ConstructingObjectParser<>("failure", true, a -> { int i = 0; String index = (String) a[i++]; Integer shardId = (Integer) a[i++]; String nodeId = (String) a[i++]; Object reason = a[i++]; Throwable reasonThrowable; if (reason instanceof String) { reasonThrowable = new RuntimeException("Unknown remote exception with reason=[" + (String) reason + "]"); } else { reasonThrowable = (Throwable) reason; } return new SearchFailure(reasonThrowable, index, shardId, nodeId); }); static { SEARCH_FAILURE_PARSER.declareString(optionalConstructorArg(), new ParseField("index")); SEARCH_FAILURE_PARSER.declareInt(optionalConstructorArg(), new ParseField("shard")); SEARCH_FAILURE_PARSER.declareString(optionalConstructorArg(), new ParseField("node")); SEARCH_FAILURE_PARSER.declareField(constructorArg(), (p, c) -> { if (p.currentToken() == XContentParser.Token.START_OBJECT) { return ThrowableBuilder.PARSER.apply(p, c); } else { return p.text(); } }, new ParseField("reason"), ValueType.OBJECT_OR_STRING); } /** * Parser for the {@code _shards} element. Throws everything out except the errors array if there is one. If there isn't one then it * parses to an empty list. */ public static final ConstructingObjectParser<List<Throwable>, XContentType> SHARDS_PARSER = new ConstructingObjectParser<>("_shards", true, a -> { @SuppressWarnings("unchecked") List<Throwable> failures = (List<Throwable>) a[0]; failures = failures == null ? emptyList() : failures; return failures; }); static { SHARDS_PARSER.declareObjectArray(optionalConstructorArg(), SEARCH_FAILURE_PARSER, new ParseField("failures")); } public static final ConstructingObjectParser<Response, XContentType> RESPONSE_PARSER = new ConstructingObjectParser<>("search_response", true, a -> { int i = 0; Throwable catastrophicFailure = (Throwable) a[i++]; if (catastrophicFailure != null) { return new Response(false, singletonList(new SearchFailure(catastrophicFailure)), 0, emptyList(), null); } boolean timedOut = (boolean) a[i++]; String scroll = (String) a[i++]; Object[] hitsElement = (Object[]) a[i++]; @SuppressWarnings("unchecked") List<SearchFailure> failures = (List<SearchFailure>) a[i++]; long totalHits = 0; List<Hit> hits = emptyList(); // Pull apart the hits element if we got it if (hitsElement != null) { i = 0; totalHits = (long) hitsElement[i++]; @SuppressWarnings("unchecked") List<Hit> h = (List<Hit>) hitsElement[i++]; hits = h; } return new Response(timedOut, failures, totalHits, hits, scroll); }); static { RESPONSE_PARSER.declareObject(optionalConstructorArg(), ThrowableBuilder.PARSER::apply, new ParseField("error")); RESPONSE_PARSER.declareBoolean(optionalConstructorArg(), new ParseField("timed_out")); RESPONSE_PARSER.declareString(optionalConstructorArg(), new ParseField("_scroll_id")); RESPONSE_PARSER.declareObject(optionalConstructorArg(), HITS_PARSER, new ParseField("hits")); RESPONSE_PARSER.declareObject(optionalConstructorArg(), SHARDS_PARSER, new ParseField("_shards")); } /** * Collects stuff about Throwables and attempts to rebuild them. */ public static class ThrowableBuilder { public static final BiFunction<XContentParser, XContentType, Throwable> PARSER; static { ObjectParser<ThrowableBuilder, XContentType> parser = new ObjectParser<>("reason", true, ThrowableBuilder::new); PARSER = parser.andThen(ThrowableBuilder::build); parser.declareString(ThrowableBuilder::setType, new ParseField("type")); parser.declareString(ThrowableBuilder::setReason, new ParseField("reason")); parser.declareObject(ThrowableBuilder::setCausedBy, PARSER::apply, new ParseField("caused_by")); // So we can give a nice error for parsing exceptions parser.declareInt(ThrowableBuilder::setLine, new ParseField("line")); parser.declareInt(ThrowableBuilder::setColumn, new ParseField("col")); } private String type; private String reason; private Integer line; private Integer column; private Throwable causedBy; public Throwable build() { Throwable t = buildWithoutCause(); if (causedBy != null) { t.initCause(causedBy); } return t; } private Throwable buildWithoutCause() { requireNonNull(type, "[type] is required"); requireNonNull(reason, "[reason] is required"); switch (type) { // Make some effort to use the right exceptions case "es_rejected_execution_exception": return new EsRejectedExecutionException(reason); case "parsing_exception": XContentLocation location = null; if (line != null && column != null) { location = new XContentLocation(line, column); } return new ParsingException(location, reason); // But it isn't worth trying to get it perfect.... default: return new RuntimeException(type + ": " + reason); } } public void setType(String type) { this.type = type; } public void setReason(String reason) { this.reason = reason; } public void setLine(Integer line) { this.line = line; } public void setColumn(Integer column) { this.column = column; } public void setCausedBy(Throwable causedBy) { this.causedBy = causedBy; } } /** * Parses the main action to return just the {@linkplain Version} that it returns. We throw everything else out. */ public static final ConstructingObjectParser<Version, XContentType> MAIN_ACTION_PARSER = new ConstructingObjectParser<>( "/", true, a -> (Version) a[0]); static { ConstructingObjectParser<Version, XContentType> versionParser = new ConstructingObjectParser<>( "version", true, a -> Version.fromString((String) a[0])); versionParser.declareString(constructorArg(), new ParseField("number")); MAIN_ACTION_PARSER.declareObject(constructorArg(), versionParser, new ParseField("version")); } }