/* * 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.search; import org.elasticsearch.rest.XContentRestResponse; import org.elasticsearch.rest.XContentThrowableRestResponse; import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestChannel; import org.elasticsearch.ElasticSearchIllegalArgumentException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.search.SearchOperationThreading; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.support.IgnoreIndices; import org.elasticsearch.client.SearchClient; import org.elasticsearch.common.Strings; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.QueryStringQueryBuilder; import org.elasticsearch.rest.*; import org.elasticsearch.rest.action.support.RestActions; import org.elasticsearch.search.Scroll; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.sort.SortOrder; import java.io.IOException; import static org.elasticsearch.common.unit.TimeValue.parseTimeValue; import static org.elasticsearch.rest.RestRequest.Method.GET; import static org.elasticsearch.rest.RestRequest.Method.POST; import static org.elasticsearch.rest.RestStatus.BAD_REQUEST; import static org.elasticsearch.rest.action.support.RestXContentBuilder.restContentBuilder; /** * */ public class RestSearchAction extends BaseRestHandler<SearchClient> { @Inject public RestSearchAction(Settings settings, SearchClient client, RestController controller) { super(settings, client); controller.registerHandler(GET, "/_search", this); controller.registerHandler(POST, "/_search", this); controller.registerHandler(GET, "/{index}/_search", this); controller.registerHandler(POST, "/{index}/_search", this); controller.registerHandler(GET, "/{index}/{type}/_search", this); controller.registerHandler(POST, "/{index}/{type}/_search", this); } @Override public void handleRequest(final RestRequest request, final RestChannel channel) { SearchRequest searchRequest; try { searchRequest = parseSearchRequest(request); searchRequest.listenerThreaded(false); SearchOperationThreading operationThreading = SearchOperationThreading.fromString(request.param("operation_threading"), null); if (operationThreading != null) { if (operationThreading == SearchOperationThreading.NO_THREADS) { // since we don't spawn, don't allow no_threads, but change it to a single thread operationThreading = SearchOperationThreading.SINGLE_THREAD; } searchRequest.operationThreading(operationThreading); } } catch (Exception e) { if (logger.isDebugEnabled()) { logger.debug("failed to parse search request parameters", e); } try { XContentBuilder builder = restContentBuilder(request); channel.sendResponse(new XContentRestResponse(request, BAD_REQUEST, builder.startObject().field("error", e.getMessage()).endObject())); } catch (IOException e1) { logger.error("Failed to send failure response", e1); } return; } client.search(searchRequest, new ActionListener<SearchResponse>() { @Override public void onResponse(SearchResponse response) { try { XContentBuilder builder = restContentBuilder(request); builder.startObject(); response.toXContent(builder, request); builder.endObject(); channel.sendResponse(new XContentRestResponse(request, response.status(), builder)); } catch (Exception e) { if (logger.isDebugEnabled()) { logger.debug("failed to execute search (building response)", 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); } } }); } private SearchRequest parseSearchRequest(RestRequest request) { String[] indices = RestActions.splitIndices(request.param("index")); SearchRequest searchRequest = new SearchRequest(indices); // get the content, and put it in the body if (request.hasContent()) { searchRequest.source(request.content(), request.contentUnsafe()); } else { String source = request.param("source"); if (source != null) { searchRequest.source(source); } } // add extra source based on the request parameters searchRequest.extraSource(parseSearchSource(request)); searchRequest.searchType(request.param("search_type")); String scroll = request.param("scroll"); if (scroll != null) { searchRequest.scroll(new Scroll(parseTimeValue(scroll, null))); } searchRequest.types(RestActions.splitTypes(request.param("type"))); searchRequest.queryHint(request.param("query_hint")); searchRequest.routing(request.param("routing")); searchRequest.preference(request.param("preference")); if (request.hasParam("ignore_indices")) { searchRequest.ignoreIndices(IgnoreIndices.fromString(request.param("ignore_indices"))); } return searchRequest; } private SearchSourceBuilder parseSearchSource(RestRequest request) { SearchSourceBuilder searchSourceBuilder = null; String queryString = request.param("q"); if (queryString != null) { QueryStringQueryBuilder queryBuilder = QueryBuilders.queryString(queryString); queryBuilder.defaultField(request.param("df")); queryBuilder.analyzer(request.param("analyzer")); queryBuilder.analyzeWildcard(request.paramAsBoolean("analyze_wildcard", false)); queryBuilder.lowercaseExpandedTerms(request.paramAsBoolean("lowercase_expanded_terms", true)); queryBuilder.lenient(request.paramAsBooleanOptional("lenient", null)); String defaultOperator = request.param("default_operator"); if (defaultOperator != null) { if ("OR".equals(defaultOperator)) { queryBuilder.defaultOperator(QueryStringQueryBuilder.Operator.OR); } else if ("AND".equals(defaultOperator)) { queryBuilder.defaultOperator(QueryStringQueryBuilder.Operator.AND); } else { throw new ElasticSearchIllegalArgumentException("Unsupported defaultOperator [" + defaultOperator + "], can either be [OR] or [AND]"); } } if (searchSourceBuilder == null) { searchSourceBuilder = new SearchSourceBuilder(); } searchSourceBuilder.query(queryBuilder); } int from = request.paramAsInt("from", -1); if (from != -1) { if (searchSourceBuilder == null) { searchSourceBuilder = new SearchSourceBuilder(); } searchSourceBuilder.from(from); } int size = request.paramAsInt("size", -1); if (size != -1) { if (searchSourceBuilder == null) { searchSourceBuilder = new SearchSourceBuilder(); } searchSourceBuilder.size(size); } if (request.hasParam("explain")) { if (searchSourceBuilder == null) { searchSourceBuilder = new SearchSourceBuilder(); } searchSourceBuilder.explain(request.paramAsBooleanOptional("explain", null)); } if (request.hasParam("version")) { if (searchSourceBuilder == null) { searchSourceBuilder = new SearchSourceBuilder(); } searchSourceBuilder.version(request.paramAsBooleanOptional("version", null)); } if (request.hasParam("timeout")) { if (searchSourceBuilder == null) { searchSourceBuilder = new SearchSourceBuilder(); } searchSourceBuilder.timeout(request.paramAsTime("timeout", null)); } String sField = request.param("fields"); if (sField != null) { if (searchSourceBuilder == null) { searchSourceBuilder = new SearchSourceBuilder(); } if (!Strings.hasText(sField)) { searchSourceBuilder.noFields(); } else { String[] sFields = Strings.splitStringByCommaToArray(sField); if (sFields != null) { for (String field : sFields) { searchSourceBuilder.field(field); } } } } String sSorts = request.param("sort"); if (sSorts != null) { if (searchSourceBuilder == null) { searchSourceBuilder = new SearchSourceBuilder(); } String[] sorts = Strings.splitStringByCommaToArray(sSorts); for (String sort : sorts) { int delimiter = sort.lastIndexOf(":"); if (delimiter != -1) { String sortField = sort.substring(0, delimiter); String reverse = sort.substring(delimiter + 1); if ("asc".equals(reverse)) { searchSourceBuilder.sort(sortField, SortOrder.ASC); } else if ("desc".equals(reverse)) { searchSourceBuilder.sort(sortField, SortOrder.DESC); } } else { searchSourceBuilder.sort(sort); } } } String sIndicesBoost = request.param("indices_boost"); if (sIndicesBoost != null) { if (searchSourceBuilder == null) { searchSourceBuilder = new SearchSourceBuilder(); } String[] indicesBoost = Strings.splitStringByCommaToArray(sIndicesBoost); for (String indexBoost : indicesBoost) { int divisor = indexBoost.indexOf(','); if (divisor == -1) { throw new ElasticSearchIllegalArgumentException("Illegal index boost [" + indexBoost + "], no ','"); } String indexName = indexBoost.substring(0, divisor); String sBoost = indexBoost.substring(divisor + 1); try { searchSourceBuilder.indexBoost(indexName, Float.parseFloat(sBoost)); } catch (NumberFormatException e) { throw new ElasticSearchIllegalArgumentException("Illegal index boost [" + indexBoost + "], boost not a float number"); } } } String sStats = request.param("stats"); if (sStats != null) { if (searchSourceBuilder == null) { searchSourceBuilder = new SearchSourceBuilder(); } searchSourceBuilder.stats(Strings.splitStringByCommaToArray(sStats)); } return searchSourceBuilder; } }