From f5665275133eac1d6071cbf2391796cb0527358d Mon Sep 17 00:00:00 2001 From: Shay Banon Date: Mon, 6 May 2013 12:55:32 +0200 Subject: [PATCH] Rest Get Source Allow to get the source directly using a specific REST endpoint without any additional content around it, the endpoint is `{index}/{type}/{id}/_source`. Note, HEAD now also support the _source endpoint. closes #2993, closes #2995 --- .../elasticsearch/action/get/GetResponse.java | 8 ++ .../rest/action/RestActionModule.java | 2 + .../indices/analyze/RestAnalyzeAction.java | 2 +- .../rest/action/get/RestGetSourceAction.java | 88 +++++++++++++++++++ .../rest/action/get/RestHeadAction.java | 1 + .../action/support/RestXContentBuilder.java | 49 +++++++++-- 6 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/elasticsearch/rest/action/get/RestGetSourceAction.java diff --git a/src/main/java/org/elasticsearch/action/get/GetResponse.java b/src/main/java/org/elasticsearch/action/get/GetResponse.java index 309aabf0d48..84401ecbd97 100644 --- a/src/main/java/org/elasticsearch/action/get/GetResponse.java +++ b/src/main/java/org/elasticsearch/action/get/GetResponse.java @@ -92,6 +92,14 @@ public class GetResponse extends ActionResponse implements Iterable, T return getResult.source(); } + /** + * Returns the internal source bytes, as they are returned without munging (for example, + * might still be compressed). + */ + public BytesReference getSourceInternal() { + return getResult.internalSourceRef(); + } + /** * Returns bytes reference, also un compress the source if needed. */ diff --git a/src/main/java/org/elasticsearch/rest/action/RestActionModule.java b/src/main/java/org/elasticsearch/rest/action/RestActionModule.java index 2d45ee21f43..633a0d5243f 100644 --- a/src/main/java/org/elasticsearch/rest/action/RestActionModule.java +++ b/src/main/java/org/elasticsearch/rest/action/RestActionModule.java @@ -68,6 +68,7 @@ import org.elasticsearch.rest.action.delete.RestDeleteAction; import org.elasticsearch.rest.action.deletebyquery.RestDeleteByQueryAction; import org.elasticsearch.rest.action.explain.RestExplainAction; import org.elasticsearch.rest.action.get.RestGetAction; +import org.elasticsearch.rest.action.get.RestGetSourceAction; import org.elasticsearch.rest.action.get.RestHeadAction; import org.elasticsearch.rest.action.get.RestMultiGetAction; import org.elasticsearch.rest.action.index.RestIndexAction; @@ -149,6 +150,7 @@ public class RestActionModule extends AbstractModule { bind(RestIndexAction.class).asEagerSingleton(); bind(RestGetAction.class).asEagerSingleton(); + bind(RestGetSourceAction.class).asEagerSingleton(); bind(RestHeadAction.class).asEagerSingleton(); bind(RestMultiGetAction.class).asEagerSingleton(); bind(RestDeleteAction.class).asEagerSingleton(); diff --git a/src/main/java/org/elasticsearch/rest/action/admin/indices/analyze/RestAnalyzeAction.java b/src/main/java/org/elasticsearch/rest/action/admin/indices/analyze/RestAnalyzeAction.java index 8e99d0a9f16..c368cf24519 100644 --- a/src/main/java/org/elasticsearch/rest/action/admin/indices/analyze/RestAnalyzeAction.java +++ b/src/main/java/org/elasticsearch/rest/action/admin/indices/analyze/RestAnalyzeAction.java @@ -76,7 +76,7 @@ public class RestAnalyzeAction extends BaseRestHandler { @Override public void onResponse(AnalyzeResponse response) { try { - XContentBuilder builder = restContentBuilder(request, false); + XContentBuilder builder = restContentBuilder(request, null); builder.startObject(); response.toXContent(builder, request); builder.endObject(); diff --git a/src/main/java/org/elasticsearch/rest/action/get/RestGetSourceAction.java b/src/main/java/org/elasticsearch/rest/action/get/RestGetSourceAction.java new file mode 100644 index 00000000000..65588d31a16 --- /dev/null +++ b/src/main/java/org/elasticsearch/rest/action/get/RestGetSourceAction.java @@ -0,0 +1,88 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.rest.action.get; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.get.GetRequest; +import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.rest.*; +import org.elasticsearch.rest.action.support.RestXContentBuilder; + +import java.io.IOException; + +import static org.elasticsearch.rest.RestRequest.Method.GET; +import static org.elasticsearch.rest.RestStatus.NOT_FOUND; +import static org.elasticsearch.rest.RestStatus.OK; +import static org.elasticsearch.rest.action.support.RestXContentBuilder.restContentBuilder; + +/** + * + */ +public class RestGetSourceAction extends BaseRestHandler { + + @Inject + public RestGetSourceAction(Settings settings, Client client, RestController controller) { + super(settings, client); + controller.registerHandler(GET, "/{index}/{type}/{id}/_source", this); + } + + @Override + public void handleRequest(final RestRequest request, final RestChannel channel) { + final GetRequest getRequest = new GetRequest(request.param("index"), request.param("type"), request.param("id")); + getRequest.listenerThreaded(false); + getRequest.operationThreaded(true); + getRequest.refresh(request.paramAsBoolean("refresh", getRequest.refresh())); + getRequest.routing(request.param("routing")); // order is important, set it after routing, so it will set the routing + getRequest.parent(request.param("parent")); + getRequest.preference(request.param("preference")); + getRequest.realtime(request.paramAsBooleanOptional("realtime", null)); + + client.get(getRequest, new ActionListener() { + @Override + public void onResponse(GetResponse response) { + + try { + XContentBuilder builder = restContentBuilder(request, response.getSourceInternal()); + if (!response.isExists()) { + channel.sendResponse(new XContentRestResponse(request, NOT_FOUND, builder)); + } else { + RestXContentBuilder.directSource(response.getSourceInternal(), builder, request); + channel.sendResponse(new XContentRestResponse(request, OK, builder)); + } + } catch (Throwable e) { + onFailure(e); + } + } + + @Override + public void onFailure(Throwable e) { + try { + channel.sendResponse(new XContentThrowableRestResponse(request, e)); + } catch (IOException e1) { + logger.error("Failed to send failure response", e1); + } + } + }); + } +} diff --git a/src/main/java/org/elasticsearch/rest/action/get/RestHeadAction.java b/src/main/java/org/elasticsearch/rest/action/get/RestHeadAction.java index c58b742911a..fdfef179ca3 100644 --- a/src/main/java/org/elasticsearch/rest/action/get/RestHeadAction.java +++ b/src/main/java/org/elasticsearch/rest/action/get/RestHeadAction.java @@ -42,6 +42,7 @@ public class RestHeadAction extends BaseRestHandler { public RestHeadAction(Settings settings, Client client, RestController controller) { super(settings, client); controller.registerHandler(HEAD, "/{index}/{type}/{id}", this); + controller.registerHandler(HEAD, "/{index}/{type}/{id}/_source", this); } @Override diff --git a/src/main/java/org/elasticsearch/rest/action/support/RestXContentBuilder.java b/src/main/java/org/elasticsearch/rest/action/support/RestXContentBuilder.java index d94a37247ff..1ff850952ae 100644 --- a/src/main/java/org/elasticsearch/rest/action/support/RestXContentBuilder.java +++ b/src/main/java/org/elasticsearch/rest/action/support/RestXContentBuilder.java @@ -19,10 +19,12 @@ package org.elasticsearch.rest.action.support; +import org.elasticsearch.common.Nullable; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.compress.CompressedStreamInput; import org.elasticsearch.common.compress.Compressor; import org.elasticsearch.common.compress.CompressorFactory; +import org.elasticsearch.common.io.Streams; import org.elasticsearch.common.io.stream.CachedStreamOutput; import org.elasticsearch.common.xcontent.*; import org.elasticsearch.rest.RestRequest; @@ -35,15 +37,16 @@ import java.io.IOException; public class RestXContentBuilder { public static XContentBuilder restContentBuilder(RestRequest request) throws IOException { - return restContentBuilder(request, true); + // use the request body as the auto detect source (if it exists) + return restContentBuilder(request, request.hasContent() ? request.content() : null); } - public static XContentBuilder restContentBuilder(RestRequest request, boolean autoDetect) throws IOException { + public static XContentBuilder restContentBuilder(RestRequest request, @Nullable BytesReference autoDetectSource) throws IOException { XContentType contentType = XContentType.fromRestContentType(request.param("format", request.header("Content-Type"))); if (contentType == null) { - // try and guess it from the body, if exists - if (autoDetect && request.hasContent()) { - contentType = XContentFactory.xContentType(request.content()); + // try and guess it from the auto detect source + if (autoDetectSource != null) { + contentType = XContentFactory.xContentType(autoDetectSource); } } if (contentType == null) { @@ -66,6 +69,42 @@ public class RestXContentBuilder { return builder; } + /** + * Directly writes the source to the output builder + */ + public static void directSource(BytesReference source, XContentBuilder rawBuilder, ToXContent.Params params) throws IOException { + Compressor compressor = CompressorFactory.compressor(source); + if (compressor != null) { + CompressedStreamInput compressedStreamInput = compressor.streamInput(source.streamInput()); + XContentType contentType = XContentFactory.xContentType(compressedStreamInput); + compressedStreamInput.resetToBufferStart(); + if (contentType == rawBuilder.contentType()) { + Streams.copy(compressedStreamInput, rawBuilder.stream()); + } else { + XContentParser parser = XContentFactory.xContent(contentType).createParser(compressedStreamInput); + try { + parser.nextToken(); + rawBuilder.copyCurrentStructure(parser); + } finally { + parser.close(); + } + } + } else { + XContentType contentType = XContentFactory.xContentType(source); + if (contentType == rawBuilder.contentType()) { + source.writeTo(rawBuilder.stream()); + } else { + XContentParser parser = XContentFactory.xContent(contentType).createParser(source); + try { + parser.nextToken(); + rawBuilder.copyCurrentStructure(parser); + } finally { + parser.close(); + } + } + } + } + public static void restDocumentSource(BytesReference source, XContentBuilder builder, ToXContent.Params params) throws IOException { Compressor compressor = CompressorFactory.compressor(source); if (compressor != null) {