ElasticsearchのPluginを作る 連載(1) Hello World

この連載では[Elasticsearch](https://www.elastic.co/)のpluginの基本的な書き方について解説していきます。第一回は、「Hello World」を表示するREST APIを作成します。
作成したソースは[github](https://github.com/taka8-0304/elasticsearch-sample)にあります。

#目的
指定したURLにアクセスすると「Hello Wolrd」と表示されるElasticsearchのPluginを作成します。

#実装概要
ElasticsearchのPluginを作成するための、Gradleプラグインを使用します。こちらのプラグインは、ElasticsearchのOSSに含まれているのですが、ドキュメント等がほとんどありませんし、メジャーバージョンアップ等で動作仕様が変更される事があります。そのため、利用する際にはそれらのリスクを考慮する必要があります。
7系のGradleプラグインは、Elasticsearchのモジュールのダウンロード元が内部URLになってしまうようです。このため、gradleプラグインで、テスト用のElasticsearchの起動に失敗します。このチュートリアルでは、別途Elasticsearchをダウンロードし、手動でPluginのインストールおよびElasticsearchの起動を行います。

#動作環境
- Elasticsearch 7.4.2
- Gradle 6.0-rc2
- OracleJDK 13(OpenJDKでも問題ない)
- Windows(コマンドの実行例のみ。プラグインの作成、実行はElasticsearchの実行環境に準じます)

**Elasticsearch 7.4.2用のGradleプラグインはJava 12以上が必要。Java 13で動作するためには、Gradle 6.0が必要)。このため、現状でElasticsearch 7.4のプラグインを作る場合にはこの組み合わせになります)**

#記事中の記法
- [～]は環境に応じた変数です。ご自分の環境に合わせて読みかえて下さい。

#事前準備
- Elasticsearchのインストール [OSS版](https://www.elastic.co/jp/downloads/elasticsearch-oss) [ELASTICSEARCH_DIR]に展開
- Gradleのインストール [RC版](https://gradle.org/release-candidate/)　[GRADLE_DIR]に展開
- JDKのインストール [OracleJDK] (https://www.oracle.com/technetwork/java/javase/downloads/index.html) [JAVA_DIR]に配置

#プロジェクトの準備
プロジェクトは[PROJECT_DIR]に配置するものとします。
### プロジェクトは以下のような構成になります
- src/main/java  jarにバンドルされるclassの元になるjavaソースを配置します。
- src/main/resources jarにバンドルされる各種リソースを配置します。
- src/main/plugin-metadata  セキュリティポリシー等のEclipseプラグインの固有設定を配置します。
- src/test/java テスト用のjavaソースを配置します。
- src/test/resources テスト用の各種リソースを配置します。
- settings.gradle プロジェクトの構成を設定するために必要なファイルです。
- build.gradle プロジェクトをビルドする際のタスク等の設定をするために必要なファイルです。

###settings.gradleを作成します
今回は単一プロジェクト構成なので、プロジェクト名のみを指定します。プロジェクト名はjarファイルのプレフィックスや、Eclipse等の開発環境にインポートした際のプロジェクト名になります。

~~~
rootProject.name = 'elasticsearch-sample-plugins'
~~~



###build.gradleを作成します
動作環境のJVMのバージョン(targetVersion)や、Elasticsearchプラグインを作成するための固有設定をします。

~~~
buildscript {
	repositories {
		mavenLocal()
		mavenCentral()
		jcenter()
	}
    dependencies {
    	// Elasticsearchはmavenのセントラルレポジトリにビルドツールを公開している。
    	// https://mvnrepository.com/artifact/org.elasticsearch.gradle/build-tools
    	classpath group: 'org.elasticsearch.gradle', name: 'build-tools', version: '7.4.2'		
    }
}

plugins {
	id 'java'
	// 依存関係の解決にmavenが必要
	id 'maven'
	// 開発環境としてEclipseを使用する場合には必要
	id 'eclipse'
}

// プラグインをビルドするためのGradleプラグインの名前
apply plugin: 'elasticsearch.esplugin'

sourceCompatibility = 1.8
// ElasticsearchのruntimeがJDK1.8でも動作するようにしておく。
targetCompatibility = 1.8

group = 'taka8.elasticsearch.plugins'
version = '0.0.1'

repositories {
	mavenLocal()
	mavenCentral()
	jcenter()
}
[compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8'

eclipse {
	 classpath {
		containers.clear()
		downloadSources = true
		downloadJavadoc = true
	}
}

ext {
	// 以下の二つを設定しないとプラグインのビルドがエラーになる
	licenseFile = project.file('LICENSE')
	noticeFile = project.file('NOTICE')
}

esplugin {
	// プラグイン名
	name project.name
	
	// プラグインの説明
	description 'Elasticsearch sample plugin'
	
	// プラグインの入口になるクラス
	classname 'taka8.elasticsearch.plugins.SamplePlugin'
}
~~~

#プラグインソースの準備
###プラグインの入口になるクラスを作成します
build.gradleのesplugin.classnameに設定されたクラス名(FQCN)のクラスを「src/main/java」以下に作成します。

``` java:SamplePlugin.java
package taka8.elasticsearch.plugins;

import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;

import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.IndexScopedSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsFilter;
import org.elasticsearch.plugins.ActionPlugin;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestHandler;

import taka8.elasticsearch.rest.HelloWorldAction;

// REST API用のプラグインを作成する際には、ActionPluginを実装する必要がある。
public class SamplePlugin extends Plugin implements ActionPlugin {

	// このプラグインで定義するRestHandlerのリストを返す
	@Override
	public List<RestHandler> getRestHandlers(Settings settings, RestController restController,
			ClusterSettings clusterSettings, IndexScopedSettings indexScopedSettings, SettingsFilter settingsFilter,
			IndexNameExpressionResolver indexNameExpressionResolver, Supplier<DiscoveryNodes> nodesInCluster) {
		return Arrays.asList(//
				// 独自定義のクラス(Hello World文字列を返却する
				new HelloWorldAction(restController));
	}

}
```

###RestHandlerを作成します

``` java:HelloWorldAction.java
package taka8.elasticsearch.rest;

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

import java.io.IOException;

import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.RestResponse;
import org.elasticsearch.rest.RestStatus;

// REST APIを受け付けるRestHandlerはBaseRestHandlerを継承すると作りやすい
public class HelloWorldAction extends BaseRestHandler {

	public HelloWorldAction(final RestController controller) {
		assert controller != null;
		// 要求を受け付けるパスを定義する。
		controller.registerHandler(GET, "/hello", this);
		controller.registerHandler(HEAD, "/hello", this);
	}

	// RestHandlerの名前を定義する。
	// 人が見て分かりやすい名前にする。
	// 使用方法を返すAPIで使用される
	@Override
	public String getName() {
		return "hello_word_action";
	}

	@Override
	protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
		return channel -> {
			RestResponse response = new BytesRestResponse(RestStatus.OK, "Hello World");
			// チャンネルに応答を書き込む
			channel.sendResponse(response);
		};
	}

}
```

# 動作確認
### 環境変数の設定
~~~
>set JAVA_HOME=[JAVA_DIR]
>set PATH=[GRADLE_DIR]\bin;%PATH%
~~~

### ビルド
~~~
>gradle install
~~~

### プラグインのinstall
~~~
>cd [ELASTICSEARCH_DIR]
>.\bin\elasticsearch-plugin install file:/[PROJECT_DIR]/build/distributions/elasticsearch-sample-plugins-0.0.1.zip
~~~
**PROJECT_DIRはURL表記にします**

### Elasticsearchの起動
~~~
>cd [ELASTICSEARCH_DIR]
>.\bin\elasticsearch
~~~

### 画面確認
ブラウザでhttp\://localhost:9200/helloにアクセスし、「Hello World」が表示される事を確認します。

#あとがき
プラグイン作成用のGradleプラグインを使って、簡単にElasticsearchのプラグインを作成する事が分かりました。これだと、実際の開発ではほとんど意味がありませんが、Elasticsearchは実はほぼプラグインと同様な作りでAPIアクセス部分が作成されており、かなり高機能なプラグインを作る事が出来ます。次回からは、より高度なプラグインの作り方を解説していきます。