/**
* personium.io
* Copyright 2014 FUJITSU LIMITED
*
* Licensed 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 com.fujitsu.dc.common.es.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.CharEncoding;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.ActionFuture;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexResponse;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.deletebyquery.DeleteByQueryResponse;
import org.elasticsearch.action.search.MultiSearchResponse;
import org.elasticsearch.action.search.SearchPhaseExecutionException;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.indices.IndexAlreadyExistsException;
import org.elasticsearch.indices.IndexMissingException;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fujitsu.dc.common.es.EsBulkRequest;
import com.fujitsu.dc.common.es.EsIndex;
import com.fujitsu.dc.common.es.query.DcQueryBuilder;
import com.fujitsu.dc.common.es.response.DcBulkResponse;
import com.fujitsu.dc.common.es.response.DcMultiSearchResponse;
import com.fujitsu.dc.common.es.response.DcSearchResponse;
import com.fujitsu.dc.common.es.response.EsClientException;
import com.fujitsu.dc.common.es.response.impl.DcBulkResponseImpl;
import com.fujitsu.dc.common.es.response.impl.DcMultiSearchResponseImpl;
import com.fujitsu.dc.common.es.response.impl.DcSearchResponseImpl;
/**
* Index 操作用 Class.
*/
public class EsIndexImpl extends EsTranslogHandler implements EsIndex {
private InternalEsClient esClient;
/**
* ログ.
*/
static Logger log = LoggerFactory.getLogger(EsIndexImpl.class);
// エラー発生時のリトライ回数
private int retryCount;
// エラー発生時のリトライ間隔
private int retryInterval;
String name;
String category;
private EsTranslogHandler requestOwner;
/**
* インデックス名とカテゴリを指定してインスタンスを生成する.
* @param name インデックス名
* @param category カテゴリ
* @param times ESでエラーが発生した場合のリトライ回数
* @param interval ESでエラーが発生した場合のリトライ間隔(ミリ秒)
* @param client EsClientオブジェクト
*/
public EsIndexImpl(final String name, final String category, int times, int interval, InternalEsClient client) {
super(times, interval, client, name);
// バッチコマンド群から参照されているためpublicとしているが参照しないこと
// (EsClientのファクトリメソッドを使用してインスタンス化すること)
this.name = name;
this.category = category;
this.retryCount = times;
this.retryInterval = interval;
this.esClient = client;
this.requestOwner = this;
}
@Override
public String getName() {
return this.name;
}
@Override
public String getCategory() {
return this.category;
}
@Override
public void create() {
if (mappingConfigs == null) {
loadMappingConfigs();
}
Map<String, JSONObject> mappings = mappingConfigs.get(this.category);
if (mappings == null) {
throw new EsClientException("NO MAPPINGS DEFINED for " + this.category + this.name);
}
CreateRetryableRequest request = new CreateRetryableRequest(retryCount, retryInterval, name, mappings);
// 必要な場合、メソッド内でリトライが行われる.
request.doRequest();
}
@Override
public void delete() {
DeleteRetryableRequest request = new DeleteRetryableRequest(retryCount, retryInterval, this.name);
// 必要な場合、メソッド内でリトライが行われる.
request.doRequest();
}
@Override
public DcSearchResponse search(String routingId, final Map<String, Object> query) {
SearchWithMapRetryableRequest request = new SearchWithMapRetryableRequest(retryCount, retryInterval, routingId,
query);
// 必要な場合、メソッド内でリトライが行われる.
return DcSearchResponseImpl.getInstance(request.doRequest());
}
@Override
public DcSearchResponse search(String routingId, final DcQueryBuilder query) {
SearchRetryableRequest request = new SearchRetryableRequest(retryCount, retryInterval, routingId,
getQueryBuilder(query));
// 必要な場合、メソッド内でリトライが行われる.
return DcSearchResponseImpl.getInstance(request.doRequest());
}
@Override
public DcMultiSearchResponse multiSearch(String routingId, final List<Map<String, Object>> queryList) {
MultiSearchRetryableRequest request =
new MultiSearchRetryableRequest(retryCount, retryInterval, routingId, queryList);
// 必要な場合、メソッド内でリトライが行われる.
return DcMultiSearchResponseImpl.getInstance(request.doRequest());
}
@Override
public void deleteByQuery(String routingId, DcQueryBuilder queryBuilder) {
QueryBuilder deleteQuery = getQueryBuilder(queryBuilder);
DeleteByQueryRetryableRequest request = new DeleteByQueryRetryableRequest(retryCount, retryInterval,
this.name, deleteQuery);
request.doRequest();
// 削除クエリと同一の検索を実行して、全件削除されていることを確認する
DcSearchResponse response = this.search(routingId, queryBuilder);
long failedCount = response.getHits().getAllPages();
if (failedCount != 0) {
throw new EsClientException.EsDeleteByQueryException(failedCount);
}
}
private QueryBuilder getQueryBuilder(DcQueryBuilder dcQueryBuilder) {
QueryBuilder queryBuilder = null;
if (dcQueryBuilder != null) {
queryBuilder = dcQueryBuilder.getQueryBuilder();
}
if (queryBuilder == null) {
log.info("Query is not specified.");
}
return queryBuilder;
}
@Override
public DcBulkResponse bulkRequest(final String routingId, final List<EsBulkRequest> datas, boolean isWriteLog) {
BulkRetryableRequest request = new BulkRetryableRequest(retryCount, retryInterval,
this.name, routingId, datas, isWriteLog);
// 必要な場合、メソッド内でリトライが行われる.
return DcBulkResponseImpl.getInstance(request.doRequest());
}
/**
* インデックスの設定を更新する.
* @param index インデックス名
* @param settings 更新するインデックス設定
* @return Void
*/
public Void updateSettings(String index, Map<String, String> settings) {
UpdateSettingsRetryableRequest request = new UpdateSettingsRetryableRequest(retryCount, retryInterval, index,
settings);
// 必要な場合、メソッド内でリトライが行われる.
return request.doRequest();
}
/**
* Elasticsearchへの index create処理実装.
*/
class CreateRetryableRequest extends AbstractRetryableEsRequest<CreateIndexResponse> {
String name;
Map<String, JSONObject> mappings;
public CreateRetryableRequest(int retryCount, long retryInterval,
String argName, Map<String, JSONObject> argMappings) {
super(retryCount, retryInterval, "EsIndex create");
name = argName;
mappings = argMappings;
}
@Override
CreateIndexResponse doProcess() {
return esClient.createIndex(name, mappings).actionGet();
}
/**
* リトライ時、引数に指定された例外を特別扱いする場合、trueを返すようにオーバーライドすること.
* これにより、#onParticularErrorメソッドが呼び出される.
* 標準実装では, 常に falseを返す.
* @param e 検査対象の例外
* @return true: 正常終了として扱う場合, false: 左記以外の場合
*/
@Override
boolean isParticularError(ElasticsearchException e) {
return e instanceof IndexAlreadyExistsException || e.getCause() instanceof IndexAlreadyExistsException;
}
@Override
CreateIndexResponse onParticularError(ElasticsearchException e) {
if (e instanceof IndexAlreadyExistsException
|| e.getCause() instanceof IndexAlreadyExistsException) {
throw new EsClientException.EsIndexAlreadyExistsException(e);
}
throw e;
}
@Override
EsTranslogHandler getEsTranslogHandler() {
return requestOwner;
}
}
/**
* Elasticsearchへの index delete処理実装.
*/
class DeleteRetryableRequest extends AbstractRetryableEsRequest<DeleteIndexResponse> {
String name;
public DeleteRetryableRequest(int retryCount, long retryInterval, String argName) {
super(retryCount, retryInterval, "EsIndex delete");
name = argName;
}
@Override
DeleteIndexResponse doProcess() {
return esClient.deleteIndex(this.name).actionGet();
}
@Override
boolean isParticularError(ElasticsearchException e) {
return e instanceof IndexMissingException || e.getCause() instanceof IndexMissingException;
}
@Override
DeleteIndexResponse onParticularError(ElasticsearchException e) {
if (e instanceof IndexMissingException || e.getCause() instanceof IndexMissingException) {
throw new EsClientException.EsIndexMissingException(e);
}
throw e;
}
@Override
EsTranslogHandler getEsTranslogHandler() {
return requestOwner;
}
}
static Map<String, Map<String, JSONObject>> mappingConfigs = null;
static synchronized void loadMappingConfigs() {
if (mappingConfigs != null) {
return;
}
mappingConfigs = new HashMap<String, Map<String, JSONObject>>();
loadMappingConfig(EsIndex.CATEGORY_AD, "Domain", "es/mapping/domain.json");
loadMappingConfig(EsIndex.CATEGORY_AD, "Cell", "es/mapping/cell.json");
loadMappingConfig(EsIndex.CATEGORY_USR, "link", "es/mapping/link.json");
loadMappingConfig(EsIndex.CATEGORY_USR, "Account", "es/mapping/account.json");
loadMappingConfig(EsIndex.CATEGORY_USR, "Box", "es/mapping/box.json");
loadMappingConfig(EsIndex.CATEGORY_USR, "Role", "es/mapping/role.json");
loadMappingConfig(EsIndex.CATEGORY_USR, "Relation", "es/mapping/relation.json");
loadMappingConfig(EsIndex.CATEGORY_USR, "SentMessage", "es/mapping/sentMessage.json");
loadMappingConfig(EsIndex.CATEGORY_USR, "ReceivedMessage", "es/mapping/receivedMessage.json");
loadMappingConfig(EsIndex.CATEGORY_USR, "EntityType", "es/mapping/entityType.json");
loadMappingConfig(EsIndex.CATEGORY_USR, "AssociationEnd", "es/mapping/associationEnd.json");
loadMappingConfig(EsIndex.CATEGORY_USR, "Property", "es/mapping/property.json");
loadMappingConfig(EsIndex.CATEGORY_USR, "ComplexType", "es/mapping/complexType.json");
loadMappingConfig(EsIndex.CATEGORY_USR, "ComplexTypeProperty", "es/mapping/complexTypeProperty.json");
loadMappingConfig(EsIndex.CATEGORY_USR, "ExtCell", "es/mapping/extCell.json");
loadMappingConfig(EsIndex.CATEGORY_USR, "ExtRole", "es/mapping/extRole.json");
loadMappingConfig(EsIndex.CATEGORY_USR, "dav", "es/mapping/dav.json");
loadMappingConfig(EsIndex.CATEGORY_USR, "UserData", "es/mapping/userdata.json");
loadMappingConfig(EsIndex.CATEGORY_USR, "_default_", "es/mapping/default.json");
}
static void loadMappingConfig(String indexCat, String typeCat, String resPath) {
JSONObject json = readJsonResource(resPath);
Map<String, JSONObject> idxMappings = mappingConfigs.get(indexCat);
if (idxMappings == null) {
idxMappings = new HashMap<String, JSONObject>();
mappingConfigs.put(indexCat, idxMappings);
}
idxMappings.put(typeCat, json);
}
/**
* プログラムリソース中のJSONで書かれた設定情報を読み出します.
* @param resPath リソースパス
* @return 読み出したJSONオブジェクト
*/
private static JSONObject readJsonResource(final String resPath) {
JSONParser jp = new JSONParser();
JSONObject json = null;
InputStream is = null;
try {
is = EsIndexImpl.class.getClassLoader().getResourceAsStream(resPath);
json = (JSONObject) jp.parse(new InputStreamReader(is, CharEncoding.UTF_8));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ParseException e) {
throw new RuntimeException(e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
return json;
}
/**
* インデックス名が最大長を超えた場合にスローする例外.
*/
public static class TooLongIndexNameException extends RuntimeException {
/**
* デフォルトシリアルバージョンID.
*/
private static final long serialVersionUID = 1L;
/**
* コンストラクタ.
* @param msg メッセージ
*/
public TooLongIndexNameException(final String msg) {
super(msg);
}
}
/**
* Elasticsearchへの search処理実装. <br />
* Queryの指定方法をMapで直接記述せずにQueryBuilderにするため、非推奨とする.
*/
@Deprecated
class SearchWithMapRetryableRequest extends AbstractRetryableEsRequest<SearchResponse> {
String routingId;
Map<String, Object> query;
public SearchWithMapRetryableRequest(int retryCount, long retryInterval,
String argRoutingId, Map<String, Object> argQuery) {
super(retryCount, retryInterval, "EsIndex search");
query = argQuery;
routingId = argRoutingId;
}
@Override
SearchResponse doProcess() {
return asyncIndexSearch(routingId, query).actionGet();
}
@Override
boolean isParticularError(ElasticsearchException e) {
return e instanceof IndexMissingException
|| e.getCause() instanceof IndexMissingException
|| e instanceof SearchPhaseExecutionException;
}
@Override
SearchResponse onParticularError(ElasticsearchException e) {
if (e instanceof IndexMissingException || e.getCause() instanceof IndexMissingException) {
return null;
}
if (e instanceof SearchPhaseExecutionException) {
throw new EsClientException("unknown property was appointed.", e);
}
throw e;
}
@Override
EsTranslogHandler getEsTranslogHandler() {
return requestOwner;
}
}
/**
* Elasticsearchへの search処理実装.
*/
class SearchRetryableRequest extends AbstractRetryableEsRequest<SearchResponse> {
String routingId;
QueryBuilder query;
public SearchRetryableRequest(int retryCount, long retryInterval,
String argRoutingId, QueryBuilder argQuery) {
super(retryCount, retryInterval, "EsIndex search");
routingId = argRoutingId;
query = argQuery;
}
@Override
boolean isParticularError(ElasticsearchException e) {
return e instanceof IndexMissingException
|| e.getCause() instanceof IndexMissingException
|| e instanceof SearchPhaseExecutionException;
}
@Override
SearchResponse doProcess() {
return asyncIndexSearch(routingId, query).actionGet();
}
@Override
SearchResponse onParticularError(ElasticsearchException e) {
if (e instanceof IndexMissingException || e.getCause() instanceof IndexMissingException) {
return null;
}
if (e instanceof SearchPhaseExecutionException) {
throw new EsClientException("unknown property was appointed.", e);
}
throw e;
}
@Override
EsTranslogHandler getEsTranslogHandler() {
return requestOwner;
}
}
/**
* Elasticsearchへの multisearch処理実装.
*/
class MultiSearchRetryableRequest extends AbstractRetryableEsRequest<MultiSearchResponse> {
String routingId;
List<Map<String, Object>> queryList;
public MultiSearchRetryableRequest(int retryCount, long retryInterval,
String argRoutingId, List<Map<String, Object>> argQueryList) {
super(retryCount, retryInterval, "EsIndex search");
routingId = argRoutingId;
queryList = argQueryList;
}
@Override
MultiSearchResponse doProcess() {
return asyncMultiIndexSearch(routingId, queryList).actionGet();
}
@Override
boolean isParticularError(ElasticsearchException e) {
return e instanceof SearchPhaseExecutionException;
}
@Override
MultiSearchResponse onParticularError(ElasticsearchException e) {
if (e instanceof SearchPhaseExecutionException) {
throw new EsClientException("unknown property was appointed.", e);
}
throw e;
}
@Override
EsTranslogHandler getEsTranslogHandler() {
return requestOwner;
}
}
/**
* Elasticsearchへの delete by query処理実装.
*/
class DeleteByQueryRetryableRequest extends AbstractRetryableEsRequest<DeleteByQueryResponse> {
String name;
QueryBuilder deleteQuery;
public DeleteByQueryRetryableRequest(int retryCount, long retryInterval,
String argName, QueryBuilder argDeleteQuery) {
super(retryCount, retryInterval, "EsIndex deleteByQuery");
name = argName;
deleteQuery = argDeleteQuery;
}
@Override
DeleteByQueryResponse doProcess() {
return esClient.deleteByQuery(name, deleteQuery);
}
@Override
EsTranslogHandler getEsTranslogHandler() {
return requestOwner;
}
}
/**
* Elasticsearchへの update index settings処理実装.
*/
class UpdateSettingsRetryableRequest extends AbstractRetryableEsRequest<Void> {
String index;
Map<String, String> settings;
public UpdateSettingsRetryableRequest(int retryCount, long retryInterval,
String index, Map<String, String> settings) {
super(retryCount, retryInterval, "EsIndex updateSettings");
this.index = index;
this.settings = settings;
}
@Override
Void doProcess() {
return esClient.updateIndexSettings(index, settings);
}
@Override
EsTranslogHandler getEsTranslogHandler() {
return requestOwner;
}
}
/**
* 非同期でドキュメントを検索. <br />
* Queryの指定方法をMapで直接記述せずにQueryBuilderにするため、非推奨とする.
* @param routingId routingId
* @param query クエリ情報
* @return ES応答
*/
public ActionFuture<SearchResponse> asyncIndexSearch(String routingId, final Map<String, Object> query) {
return esClient.asyncSearch(this.name, routingId, query);
}
/**
* 非同期でドキュメントを検索.
* @param routingId routingId
* @param query クエリ情報
* @return ES応答
*/
public ActionFuture<SearchResponse> asyncIndexSearch(String routingId, final QueryBuilder query) {
return esClient.asyncSearch(this.name, routingId, query);
}
/**
* 非同期でドキュメントをマルチ検索.
* @param routingId routingId
* @param queryList クエリ情報一覧
* @return ES応答
*/
public ActionFuture<MultiSearchResponse> asyncMultiIndexSearch(String routingId,
final List<Map<String, Object>> queryList) {
return esClient.asyncMultiSearch(this.name, routingId, queryList);
}
/**
* Elasticsearchへの bulk create処理実装.
*/
class BulkRetryableRequest extends AbstractRetryableEsRequest<BulkResponse> {
String name;
String routingId;
List<EsBulkRequest> datas;
boolean isWriteLog;
public BulkRetryableRequest(int retryCount, long retryInterval,
String argName, String argRoutingId, List<EsBulkRequest> argDatas, boolean isWriteLog) {
super(retryCount, retryInterval, "EsIndex bulkCreate");
this.name = argName;
this.routingId = argRoutingId;
this.datas = argDatas;
this.isWriteLog = isWriteLog;
}
@Override
BulkResponse doProcess() {
return esClient.bulkRequest(name, routingId, datas, isWriteLog);
}
@Override
EsTranslogHandler getEsTranslogHandler() {
return requestOwner;
}
}
}