/**
* 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.core.model.impl.es.accessor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fujitsu.dc.common.ads.AdsWriteFailureLogException;
import com.fujitsu.dc.common.ads.AdsWriteFailureLogInfo;
import com.fujitsu.dc.common.ads.AdsWriteFailureLogWriter;
import com.fujitsu.dc.common.es.EsBulkRequest;
import com.fujitsu.dc.common.es.EsIndex;
import com.fujitsu.dc.common.es.EsType;
import com.fujitsu.dc.common.es.query.DcQueryBuilder;
import com.fujitsu.dc.common.es.response.DcActionResponse;
import com.fujitsu.dc.common.es.response.DcBulkItemResponse;
import com.fujitsu.dc.common.es.response.DcBulkResponse;
import com.fujitsu.dc.common.es.response.DcDeleteResponse;
import com.fujitsu.dc.common.es.response.DcGetResponse;
import com.fujitsu.dc.common.es.response.DcIndexResponse;
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.util.DcUUID;
import com.fujitsu.dc.core.DcCoreConfig;
import com.fujitsu.dc.core.DcCoreException;
import com.fujitsu.dc.core.DcCoreLog;
import com.fujitsu.dc.core.model.impl.es.EsModel;
import com.fujitsu.dc.core.model.impl.es.ads.AdsConnectionException;
import com.fujitsu.dc.core.model.impl.es.ads.AdsException;
import com.fujitsu.dc.core.model.impl.es.ads.JdbcAds;
import com.fujitsu.dc.core.model.impl.es.doc.EntitySetDocHandler;
import com.fujitsu.dc.core.model.impl.es.doc.LinkDocHandler;
import com.fujitsu.dc.core.model.lock.Lock;
import com.fujitsu.dc.core.model.lock.LockKeyComposer;
/**
* データストア層の基本処理を実装した基底クラス.
*/
public class DataSourceAccessor {
private EsIndex index;
private EsType type;
private JdbcAds ads;
private String routingid;
/** ログ用オブジェクト. */
static Logger log = LoggerFactory.getLogger(DataSourceAccessor.class);
/**
* コンストラクタ.
* @param index インデックス
*/
public DataSourceAccessor(EsIndex index) {
this.index = index;
try {
if (DcCoreConfig.getEsAdsType().equals(DcCoreConfig.ES.ADS.TYPE_JDBC)) {
ads = new JdbcAds();
} else {
ads = null;
}
} catch (AdsConnectionException ex) {
// 初回接続エラー時は接続エラーのログを出力する.
DcCoreLog.Server.ADS_CONNECTION_ERROR.params(ex.getMessage()).reason(ex).writeLog();
throw DcCoreException.Server.ADS_CONNECTION_ERROR;
}
}
/**
* コンストラクタ.
* @param index インデックス
* @param name タイプ名
* @param routingId routingID
*/
protected DataSourceAccessor(EsIndex index, String name, String routingId) {
this.index = index;
int times = Integer.valueOf(DcCoreConfig.getESRetryTimes());
int interval = Integer.valueOf(DcCoreConfig.getESRetryInterval());
this.type = EsModel.type(index.getName(), name, routingId, times, interval);
this.routingid = routingId;
try {
if (DcCoreConfig.getEsAdsType().equals(DcCoreConfig.ES.ADS.TYPE_JDBC)) {
ads = new JdbcAds();
} else {
ads = null;
}
} catch (AdsConnectionException ex) {
// 初回接続エラー時は接続エラーのログを出力する.
DcCoreLog.Server.ADS_CONNECTION_ERROR.params(ex.getMessage()).reason(ex).writeLog();
throw DcCoreException.Server.ADS_CONNECTION_ERROR;
}
}
/**
* Adsのゲッター.
* @return JdbcAds
*/
protected JdbcAds getAds() {
return this.ads;
}
/**
* Adsのセッター.
* @param ads JdbcAds
*/
protected void setAds(JdbcAds ads) {
this.ads = ads;
}
/**
* Indexのゲッター.
* @return EsIndex
*/
public EsIndex getIndex() {
return this.index;
}
/**
* Typeを取得する.
* @return 応答
*/
public String getType() {
return this.type.getType();
}
/**
* ESへの検索時に使用するルーティングIDを取得する.
* @return ルーティングID
*/
protected String getRoutingId() {
return this.routingid;
}
/**
* ドキュメントの1件取得.
* @param id ドキュメントのID
* @return 応答
*/
public DcGetResponse get(final String id) {
try {
return this.type.get(id);
} catch (EsClientException.EsNoResponseException e) {
throw DcCoreException.Server.ES_RETRY_OVER.params(e.getMessage());
}
}
/**
* ドキュメント新規作成.
* @param data ドキュメント
* @return ES応答
*/
@SuppressWarnings("rawtypes")
public DcIndexResponse create(final Map data) {
String id = DcUUID.randomUUID();
return this.create(id, data);
}
/**
* ドキュメント新規作成.
* @param id ID
* @param data ドキュメント
* @return ES応答
*/
@SuppressWarnings({"rawtypes" })
public DcActionResponse createForDavNodeFile(final String id, final Map data) {
DcIndexResponse res = create(id, data);
return res;
}
/**
* ドキュメント新規作成.
* @param id ID
* @param data ドキュメント
* @return ES応答
*/
@SuppressWarnings({"rawtypes" })
public DcIndexResponse create(final String id, final Map data) {
try {
return this.type.create(id, data);
} catch (EsClientException.EsSchemaMismatchException e) {
throw DcCoreException.OData.SCHEMA_MISMATCH;
} catch (EsClientException.EsIndexMissingException e) {
DcCoreLog.Server.ES_INDEX_NOT_EXIST.params(this.index.getName()).writeLog();
try {
this.index.create();
createAdsIndex(null);
return this.type.create(id, data);
} catch (EsClientException.EsNoResponseException esRetry) {
throw DcCoreException.Server.ES_RETRY_OVER.params(esRetry.getMessage());
}
} catch (EsClientException.EsNoResponseException e) {
throw DcCoreException.Server.ES_RETRY_OVER.params(e.getMessage());
}
}
/**
* ドキュメント新規作成(Cell作成用).
* @param id ID
* @param data ドキュメント
* @param docHandler ドキュメントハンドラ
* @return ES応答
*/
@SuppressWarnings({"rawtypes" })
public DcIndexResponse create(final String id, final Map data, final EntitySetDocHandler docHandler) {
try {
return this.type.create(id, data);
} catch (EsClientException.EsSchemaMismatchException e) {
throw DcCoreException.OData.SCHEMA_MISMATCH;
} catch (EsClientException.EsIndexMissingException e) {
DcCoreLog.Server.ES_INDEX_NOT_EXIST.params(this.index.getName()).writeLog();
try {
this.index.create();
createAdsIndex(docHandler.getUnitUserName());
return this.type.create(id, data);
} catch (EsClientException.EsNoResponseException esRetry) {
throw DcCoreException.Server.ES_RETRY_OVER.params(esRetry.getMessage());
}
} catch (EsClientException.EsNoResponseException e) {
throw DcCoreException.Server.ES_RETRY_OVER.params(e.getMessage());
}
}
/**
* ドキュメント更新.
* @param id ID
* @param data ドキュメント
* @param version version番号
* @return ES応答
*/
@SuppressWarnings({"rawtypes" })
public DcIndexResponse update(final String id, final Map data, final long version) {
try {
return this.type.update(id, data, version);
} catch (EsClientException.EsSchemaMismatchException e) {
throw DcCoreException.OData.SCHEMA_MISMATCH;
} catch (EsClientException.EsIndexMissingException e) {
DcCoreLog.Server.ES_INDEX_NOT_EXIST.params(this.index.getName()).writeLog();
try {
this.index.create();
createAdsIndex(null);
return this.type.update(id, data, version);
} catch (EsClientException.EsNoResponseException esRetry) {
throw DcCoreException.Server.ES_RETRY_OVER.params(esRetry.getMessage());
}
} catch (EsClientException.EsNoResponseException e) {
throw DcCoreException.Server.ES_RETRY_OVER.params(e.getMessage());
}
}
/**
* ドキュメント更新.
* @param id ID
* @param data ドキュメント
* @return ES応答
*/
@SuppressWarnings("rawtypes")
public DcIndexResponse update(final String id, final Map data) {
return this.update(id, data, -1);
}
/**
* ドキュメントの件数を取得.
* @param query クエリ情報
* @return ES応答
*/
public long count(final Map<String, Object> query) {
Map<String, Object> requestQuery = null;
if (query != null) {
requestQuery = new HashMap<String, Object>(query);
} else {
requestQuery = new HashMap<String, Object>();
}
requestQuery.put("size", 0);
try {
DcSearchResponse hit = this.type.search(requestQuery);
return hit.getHits().getAllPages();
} catch (EsClientException.EsNoResponseException e) {
throw DcCoreException.Server.ES_RETRY_OVER.params(e.getMessage());
}
}
/**
* ドキュメントを検索.
* @param query クエリ情報
* @return ES応答
*/
public DcSearchResponse search(final Map<String, Object> query) {
Map<String, Object> requestQuery = null;
if (query != null) {
requestQuery = new HashMap<String, Object>(query);
} else {
requestQuery = new HashMap<String, Object>();
}
if (!requestQuery.containsKey("size")) {
requestQuery.put("size", this.count(query));
}
try {
return this.type.search(requestQuery);
} catch (EsClientException.EsNoResponseException e) {
throw DcCoreException.Server.ES_RETRY_OVER.params(e.getMessage());
}
}
/**
* ドキュメントをマルチ検索.
* 本メソッド時使用時には取得件数(size)をクエリに指定して呼び出すこと
* @param queryList クエリ情報の一覧
* @return ES応答
*/
public DcMultiSearchResponse multiSearch(final List<Map<String, Object>> queryList) {
try {
return this.type.multiSearch(queryList);
} catch (EsClientException.EsNoResponseException e) {
throw DcCoreException.Server.ES_RETRY_OVER.params(e.getMessage());
}
}
/**
* ESのインデックスに対してドキュメントを検索する.
* @param query クエリ情報
* @return ES応答
*/
public DcSearchResponse indexSearch(final Map<String, Object> query) {
Map<String, Object> requestQuery = null;
if (query == null) {
requestQuery = new HashMap<String, Object>();
} else {
requestQuery = new HashMap<String, Object>(query);
}
if (!requestQuery.containsKey("size")) {
requestQuery.put("size", this.count(query));
}
try {
return this.index.search(null, requestQuery);
} catch (EsClientException.EsNoResponseException e) {
throw DcCoreException.Server.ES_RETRY_OVER.params(e.getMessage());
}
}
/**
* Delete a document.
* @param docId Document id to delete
* @param version The version of the document to delete
* @return 応答
*/
public DcDeleteResponse delete(final String docId, final long version) {
try {
return this.type.delete(docId, version);
} catch (EsClientException.EsIndexMissingException e) {
DcCoreLog.Server.ES_INDEX_NOT_EXIST.params(this.index.getName()).writeLog();
return null;
} catch (EsClientException.EsNoResponseException e) {
throw DcCoreException.Server.ES_RETRY_OVER.params(e.getMessage());
}
}
/**
* バルクでデータを登録する.<br />
* 更新、削除は未サポート.
* @param esBulkRequest ES用バルク登録ドキュメントリスト
* @param adsBulkRequest ADS用バルク登録ドキュメントリスト
* @param routingId routingId
* @return バルクレスポンス
*/
public DcBulkResponse bulkCreate(List<EsBulkRequest> esBulkRequest,
List<EntitySetDocHandler> adsBulkRequest,
String routingId) {
// マスタ書き込みでエラーが発生したためES更新を不可能とする
prepareDataUpdate(this.index.getName());
DcBulkResponse response = null;
try {
response = this.index.bulkRequest(routingId, esBulkRequest, false);
} catch (EsClientException.EsNoResponseException e) {
throw DcCoreException.Server.ES_RETRY_OVER.params(e.getMessage());
}
if (this.ads != null) {
try {
this.ads.bulkEntity(this.index.getName(), adsBulkRequest);
} catch (AdsException e) {
DcCoreLog.Server.DATA_STORE_ENTITY_BULK_CREATE_FAIL.params(e.getMessage()).reason(e).writeLog();
for (EntitySetDocHandler docHandler : adsBulkRequest) {
// Adsの登録に失敗した場合は、専用のログに書込む
String lockKey = LockKeyComposer.fullKeyFromCategoryAndKey(Lock.CATEGORY_ODATA,
docHandler.getCellId(), null, docHandler.getNodeId());
AdsWriteFailureLogInfo loginfo = new AdsWriteFailureLogInfo(
this.getIndex().getName(), docHandler.getType(), lockKey,
docHandler.getCellId(), docHandler.getId(),
AdsWriteFailureLogInfo.OperationKind.CREATE, 1, docHandler.getUpdated());
recordAdsWriteFailureLog(loginfo);
}
}
}
return response;
}
/**
* バルクでデータを登録/更新する.<br />
* 削除は未サポート.
* @param esBulkRequest ES用バルク登録ドキュメントリスト
* @param adsBulkEntityRequest ADS用バルク更新ドキュメントリスト(Entity)
* @param adsBulkLinkRequest ADS用バルク登録ドキュメントリスト(Link)
* @param routingId routingId
* @return バルクレスポンス
*/
public DcBulkResponse bulkUpdateLink(List<EsBulkRequest> esBulkRequest,
List<EntitySetDocHandler> adsBulkEntityRequest,
List<LinkDocHandler> adsBulkLinkRequest,
String routingId) {
// マスタ書き込みでエラーが発生したためES更新を不可能とする
prepareDataUpdate(this.index.getName());
DcBulkResponse response = null;
try {
response = this.index.bulkRequest(routingId, esBulkRequest, false);
} catch (EsClientException.EsNoResponseException e) {
throw DcCoreException.Server.ES_RETRY_OVER.params(e.getMessage());
}
if (this.ads != null) {
try {
// Entityテーブル更新
if (adsBulkEntityRequest.size() > 0) {
this.ads.bulkUpdateEntity(this.index.getName(), adsBulkEntityRequest);
}
} catch (AdsException e) {
DcCoreLog.Server.DATA_STORE_ENTITY_BULK_CREATE_FAIL.params(e.getMessage()).reason(e).writeLog();
// Adsの登録に失敗した場合は、専用のログに書込む
// ESでのバージョン情報を取得するためにesBulkRequestをループさせている
DcBulkItemResponse[] responseItems = response.items();
int responseIndex = 0;
int adsBulkEntityRequestIndex = 0;
for (EsBulkRequest request : esBulkRequest) {
if (request.getType().equals(EsModel.TYPE_CTL_LINK)) {
responseIndex++;
continue;
}
DcBulkItemResponse itemResponse = responseItems[responseIndex++];
EntitySetDocHandler docHandler = adsBulkEntityRequest.get(adsBulkEntityRequestIndex++);
String lockKey = LockKeyComposer.fullKeyFromCategoryAndKey(Lock.CATEGORY_ODATA,
docHandler.getCellId(), null, docHandler.getNodeId());
AdsWriteFailureLogInfo loginfo = new AdsWriteFailureLogInfo(
this.getIndex().getName(), docHandler.getType(), lockKey,
docHandler.getCellId(), docHandler.getId(),
AdsWriteFailureLogInfo.OperationKind.UPDATE, itemResponse.version(),
docHandler.getUpdated());
recordAdsWriteFailureLog(loginfo);
}
}
try {
// Linkテーブル追加
if (adsBulkLinkRequest.size() > 0) {
this.ads.bulkCreateLink(this.index.getName(), adsBulkLinkRequest);
}
} catch (AdsException e) {
DcCoreLog.Server.DATA_STORE_ENTITY_BULK_CREATE_FAIL.params(e.getMessage()).reason(e).writeLog();
for (LinkDocHandler docHandler : adsBulkLinkRequest) {
// Adsの登録に失敗した場合は、専用のログに書込む
String lockKey = LockKeyComposer.fullKeyFromCategoryAndKey(Lock.CATEGORY_ODATA,
docHandler.getCellId(), null, docHandler.getNodeId());
AdsWriteFailureLogInfo loginfo = new AdsWriteFailureLogInfo(
this.getIndex().getName(), EsModel.TYPE_CTL_LINK, lockKey,
docHandler.getCellId(), docHandler.getId(),
AdsWriteFailureLogInfo.OperationKind.CREATE, 1, docHandler.getUpdated());
recordAdsWriteFailureLog(loginfo);
}
}
}
return response;
}
/**
* 指定されたクエリを使用してデータの削除を行う.
* @param routingId routingId
* @param deleteQuery 削除対象を指定するクエリ
*/
protected void deleteByQuery(String routingId, DcQueryBuilder deleteQuery) {
this.index.deleteByQuery(routingId, deleteQuery);
}
/**
* 指定されたIDのセルのリソースを削除する.
* @param cellId 削除対象のセルID
* @param unitUserName ユニットユーザ名
* @throws AdsException 削除に失敗
*/
protected void cellBulkDeletionAds(String cellId, String unitUserName) throws AdsException {
this.ads.deleteCellResourceFromEntity(unitUserName, cellId);
this.ads.deleteCellResourceFromDavNode(unitUserName, cellId);
this.ads.deleteCellResourceFromLink(unitUserName, cellId);
}
/**
* インデックスに対して検索リクエストを実行する.
* @param routingId routingId
* @param query 検索クエリ
* @return 検索結果
*/
public DcSearchResponse searchForIndex(String routingId, Map<String, Object> query) {
try {
if (!query.containsKey("size")) {
try {
// サイズの指定がない場合は、全件取得するようsizeを設定
query.put("size", 0);
DcSearchResponse hit = this.index.search(routingId, query);
query.put("size", hit.getHits().getAllPages());
} catch (EsClientException.EsNoResponseException e) {
throw DcCoreException.Server.ES_RETRY_OVER.params(e.getMessage());
}
}
return this.index.search(routingId, query);
} catch (EsClientException.EsNoResponseException e) {
throw DcCoreException.Server.ES_RETRY_OVER.params(e.getMessage());
}
}
/**
* インデックスに対してマルチ検索リクエストを実行する.
* @param routingId routingId
* @param queryList 検索クエリ一覧
* @return 検索結果
*/
public DcMultiSearchResponse multiSearchForIndex(String routingId, List<Map<String, Object>> queryList) {
try {
return this.index.multiSearch(routingId, queryList);
} catch (EsClientException.EsNoResponseException e) {
throw DcCoreException.Server.ES_RETRY_OVER.params(e.getMessage());
}
}
/**
* 引数で渡されたUnitUser名でADS上にUnitUserを作成する.
* @param unitUserName UnitUser名。nullの場合はEsIndexの値を使用する。
*/
protected void createAdsIndex(String unitUserName) {
if (ads == null) {
return;
}
String indexName = this.index.getName();
if (unitUserName != null) {
indexName = unitUserName;
}
try {
ads.createIndex(indexName);
} catch (AdsException adsEx) {
// TODO エラー処理が必要?参照モードにする必要があるのでは?要検討。
DcCoreLog.Server.FAILED_TO_CREATE_ADS.params(indexName).reason(adsEx).writeLog();
}
}
/**
* データの登録/更新/削除実行前の処理.
* @param unitId unitId
*/
protected void prepareDataUpdate(final String unitId) {
checkAdsConnection();
}
/**
* Adsの接続確認を行う.
*/
protected void checkAdsConnection() {
try {
if (ads != null) {
ads.checkConnection();
}
} catch (AdsException e) {
// 接続に失敗した場合はエラーレスポンスを返却する
DcCoreLog.Server.ADS_CONNECTION_ERROR.params(e.getMessage()).reason(e).writeLog();
throw DcCoreException.Server.ADS_CONNECTION_ERROR.reason(e);
}
}
/**
* Ads書込みエラー時にファイルにリペア用のエラー情報を書込む.
* @param loginfo リペア用のエラー情報
*/
protected void recordAdsWriteFailureLog(AdsWriteFailureLogInfo loginfo) {
AdsWriteFailureLogWriter adsWriteFailureLogWriter = AdsWriteFailureLogWriter.getInstance(
DcCoreConfig.getAdsWriteFailureLogDir(),
DcCoreConfig.getCoreVersion(),
DcCoreConfig.getAdsWriteFailureLogPhysicalDelete());
try {
adsWriteFailureLogWriter.writeActiveFile(loginfo);
} catch (AdsWriteFailureLogException e2) {
DcCoreLog.Server.WRITE_ADS_FAILURE_LOG_ERROR.reason(e2).writeLog();
DcCoreLog.Server.WRITE_ADS_FAILURE_LOG_INFO.params(loginfo.toString());
}
}
}