Creating an Elasticsearch Plugin Series (2) Search

In this series, I will explain the basic way to write plugins in Elasticsearch. Last time explained how to make a Plugin roughly by creating a simple Plugin that displays Hello World. Part 2 will create a Plugin that accepts simple search requests. The created source can be found on github (the previous one has been modified).

Purpose

Elasticsearch can handle most search requests with JSON-formatted requests. However, if it is standard, maintenance functions such as index deletion will be visible, and it is not desirable to access Elasticsearch directly from the WEB. Therefore, normally, Elasticsearch is used as an image like a database in the form of WEB → AP server → Elasticsearch. In addition, Elasticsearch can easily guarantee availability by using a cluster, but AP server needs to guarantee availability by some original method. This puts a heavy operational load and increases the number of monitoring targets. If Elasticsearch can have the function of AP server, it should be possible to create an application that meets some business requirements only by managing Elasticsearch. From this time, we will verify how much things can be done with plugins with the goal of having Elasticsearch have an AP server. First, let's create a REST API that performs a full-text search on the "text" field of the document with the query parameter (text).

Implementation overview

The framework of the plugin is the same as last time. The difference is that RestHandler sends a "search request" to the index node. I generate a "search request" for an index node from a REST request, but I can easily generate a "search request" from an Elasticsearch JSON-formatted REST request. This time I will use it.

Operating environment

It's almost the same as last time, but since the 6.1 version of Gradle was released, I used that.

Notation in the article

-[~] is a variable according to the environment. Please read according to your own environment.

Advance preparation

--Install Elasticsearch OSS version Expand to [ELASTICSEARCH_DIR] --Install Gradle Release version Expand to [GRADLE_DIR] --Install JDK OracleJDK Place in [JAVA_DIR]

Project preparation

Last time Use the created project.

Plugin source preparation

Create a utility for generating JSON-formatted requests.

It's easier to see when loading a template from a file, but Elasticsearch has fairly restricted access to the standard java API from plugins. You can make it accessible in the settings, but this time we will embed it in the source. I will write about relaxing access restrictions to the API from the next time onwards.

QueryTemplate.java


package taka8.elasticsearch.rest;

public class QueryTemplate {

//	{
//	  "query" : {
//	    "match" : {
//	      "text" : {
//	        "query" : "[[text]]",
//	        "operator" : "and"
//	      }
//	    }
//	  }
//	}
	private static final String TEXT_ONLY = "{\"query\":{\"match\":{\"text\":{\"query\":\"[[text]]\",\"operator\":\"and\"}}}}";

	public static String getTextOnly(String text) {
		return QueryTemplate.TEXT_ONLY.replace("[[text]]", text);
	}
}

Create a RestHandler

TextOnlySearchAction.java


package taka8.elasticsearch.rest;

import static org.elasticsearch.rest.RestRequest.Method.GET;

import java.io.IOException;

import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.DeprecationHandler;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.action.RestStatusToXContentListener;
import org.elasticsearch.search.builder.SearchSourceBuilder;

//RestHandler that accepts REST API is easy to create by inheriting BaseRestHandler
public class TextOnlySearchAction extends BaseRestHandler {

	public TextOnlySearchAction(final RestController controller) {
		assert controller != null;
		//Define the path to accept the request.
		controller.registerHandler(GET, "/{index}/search_text", this);
	}

	//Define the name of RestHandler.
	//Make the name easy for people to see.
	//Used in APIs that return usage
	@Override
	public String getName() {
		return "search_text_action";
	}

	@Override
	protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
		SearchRequest searchRequest = new SearchRequest();
		request.withContentOrSourceParamParserOrNull(parser -> _parseSearchRequest(searchRequest, request, parser));
		return channel -> {
			//Generate a Listener that creates a REST API return value from the search results.
			//Here, it is a standard Listener that returns the search result as it is.
			RestStatusToXContentListener<SearchResponse> listener = new RestStatusToXContentListener<>(channel);
			//Throw a request to the index node. Elasticsearch guarantees search performance and availability by dividing the index and arranging it on multiple nodes.
			//Therefore, it is necessary to send a request from the node that accepts the REST request to the node that actually performs the search.
			client.search(searchRequest, listener);
		};
	}

	//Initialize the search request to the index node from the REST input.
	private void _parseSearchRequest(SearchRequest searchRequest, RestRequest request,
			XContentParser requestContentParser) throws IOException {
		if (searchRequest.source() == null) {
			searchRequest.source(new SearchSourceBuilder());
		}
		//Generate an Elasticsearch JSON-formatted search request from the text parameter of the query.
		String source = QueryTemplate.getTextOnly(request.param("text"));
		//Parser for search requests in JSON format.
		XContentParser parser = XContentType.JSON.xContent().createParser(request.getXContentRegistry(),
				DeprecationHandler.THROW_UNSUPPORTED_OPERATION, source);
		searchRequest.source().parseXContent(parser);
		searchRequest.indices(Strings.splitStringByCommaToArray(request.param("index")));
	}

}

Operation check

Deploy

Follow the procedure for checking the operation of Last time to install Elasticsearch.

Registration of documents to be searched

Use a tool that can issue a POST request to POST the json format document to the following URL. http://localhost:9200/test/_bulk

{"create": {"_id": "1"}}
{"text": "Get in the car"}
{"create": {"_id": "2"}}
{"text": "Get off the bicycle"}

Screen check

Access "http : // localhost: 9200 / test / search_text? Text = car" in your browser and check that the document containing "car" is searched as below (the following formats JSON). doing).

{
  "took": 43,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 1.0921589,
    "hits": [
      {
        "_index": "test",
        "_type": "_doc",
        "_id": "1",
        "_score": 1.0921589,
        "_source": {
          "text": "Get in the car"
        }
      }
    ]
  }
}

Afterword

I made a JSON format request from a query with a plugin and confirmed that it can be searched correctly using it. This seems to satisfy the search with standard business requirements. Next, try generating a return value that meets your business requirements.

Recommended Posts

Creating an Elasticsearch Plugin Series (2) Search
Creating ElasticSearch index from Java
[Rails] Creating a search box
Make your own Elasticsearch plugin