/**
* 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.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import com.fujitsu.dc.common.ads.AdsWriteFailureLogInfo;
import com.fujitsu.dc.common.es.EsBulkRequest;
import com.fujitsu.dc.common.es.EsBulkRequest.BULK_REQUEST_TYPE;
import com.fujitsu.dc.common.es.EsIndex;
import com.fujitsu.dc.common.es.response.DcBulkItemResponse;
import com.fujitsu.dc.common.es.response.DcBulkResponse;
import com.fujitsu.dc.common.es.response.EsClientException;
import com.fujitsu.dc.core.DcCoreConfig;
import com.fujitsu.dc.core.DcCoreException;
import com.fujitsu.dc.core.DcCoreLog;
import com.fujitsu.dc.core.model.file.BinaryDataAccessException;
import com.fujitsu.dc.core.model.file.BinaryDataAccessor;
import com.fujitsu.dc.core.model.impl.es.DavNode;
import com.fujitsu.dc.core.model.impl.es.ads.AdsException;
import com.fujitsu.dc.core.model.lock.Lock;
import com.fujitsu.dc.core.model.lock.LockKeyComposer;
/**
* DavNode情報をMOVEする場合のアクセス処理を実装したクラス.
*/
public class DavMoveAccessor extends DavNodeAccessor {
DavNode srcParentNodeForRollback;
DavNode dstParentNodeForRollback;
DavNode srcNodeForRollback;
DavNode dstNoeForRollback;
// ES用バルク登録ドキュメントリスト
List<EsBulkRequest> esBulkRequest = new ArrayList<EsBulkRequest>();
// ADS用バルク更新ドキュメントリスト
List<DavNode> adsBulkRequest = new ArrayList<DavNode>();
/**
* コンストラクタ.
* @param index インデックス
* @param name タイプ名
* @param routingId routingID
*/
public DavMoveAccessor(EsIndex index, String name, String routingId) {
super(index, name, routingId);
}
/**
* 移動元の親DavNodeをロールバック用に格納する. <br />
* 値が設定されていない場合はロールバック対象とはしない。
* @param davNode DavNode
*/
public void setSourceParentNodeForRollback(DavNode davNode) {
this.srcParentNodeForRollback = cloneDavNode(davNode);
}
/**
* 移動先の親DavNodeをロールバック用に格納する. <br />
* 値が設定されていない場合はロールバック対象とはしない。
* @param davNode DavNode
*/
public void setDestinationParentNodeForRollback(DavNode davNode) {
this.dstParentNodeForRollback = cloneDavNode(davNode);
}
/**
* 移動対象のDavNodeをロールバック用に格納する. <br />
* 値が設定されていない場合はロールバック対象とはしない。
* @param davNode DavNode
*/
public void setSourceNodeForRollback(DavNode davNode) {
this.srcNodeForRollback = cloneDavNode(davNode);
}
/**
* 移動先のDavNodeをロールバック用に格納する. <br />
* 値が設定されていない場合はロールバック対象とはしない。
* @param davNode DavNode
*/
public void setDestinationNodeForRollback(DavNode davNode) {
this.dstNoeForRollback = cloneDavNode(davNode);
}
/**
* DavNodeの複製を作成する.
* @param davNode
* @return 複製したDavNode
*/
private DavNode cloneDavNode(DavNode davNode) {
if (null == davNode) {
return null;
}
String jsonString = davNode.getSource().toJSONString();
return DavNode.createFromJsonString(davNode.getId(), jsonString);
}
/**
* DavNodeの移動用リクエストをAccessorに設定する.
* @param srcName 移動対象DavNodeの名前
* @param dstName 移動対象DavNodeの名前
* @param srcNode 移動対象DavNode
* @param dstNode 移動先のDavNodeの階層情報
* @param srcParentNode 移動対象DavNodeの親DavNode
* @param dstParentNode 移動先DavNodeの親DavNode
*/
public void setMoveRequest(
String srcName,
String dstName,
DavNode srcNode,
DavNode dstNode,
DavNode srcParentNode,
DavNode dstParentNode) {
long now = new Date().getTime();
// MOVEのパターンによって更新内容を作成し分ける
if (srcNode.getParentId().equals(dstParentNode.getId())) {
// 移動せず変名のみの場合
// 親ノードのchildren情報から、移動対象のリソース情報を削除する
Map<String, String> srcChildren = srcParentNode.getChildren();
srcChildren.remove(srcName);
// 親ノードのchildren情報に、移動対象のリソース情報を追加する
srcChildren.put(dstName, srcNode.getId());
srcParentNode.setChildren(srcChildren);
srcParentNode.setUpdated(now);
adsBulkRequest.add(srcParentNode);
esBulkRequest.add(srcParentNode);
} else {
// 異なるコレクションに移動する場合
// 移動前の親ノードのchildren情報から、移動対象のリソース情報を削除する
Map<String, String> srcChildren = srcParentNode.getChildren();
srcChildren.remove(srcName);
srcParentNode.setChildren(srcChildren);
srcParentNode.setUpdated(now);
adsBulkRequest.add(srcParentNode);
esBulkRequest.add(srcParentNode);
setDestinationParentNodeForRollback(dstParentNode);
// 移動先の親ノードのchildren情報に、移動対象のリソース情報を追加する
Map<String, String> dstChildren = dstParentNode.getChildren();
dstChildren.put(dstName, srcNode.getId());
dstParentNode.setChildren(dstChildren);
dstParentNode.setUpdated(now);
adsBulkRequest.add(dstParentNode);
esBulkRequest.add(dstParentNode);
setSourceNodeForRollback(srcNode);
// 移動対象のリソースの親リソース情報を移動先のものに変更する
srcNode.setParentId(dstParentNode.getId());
srcNode.setUpdated(now);
adsBulkRequest.add(srcNode);
esBulkRequest.add(srcNode);
}
// 移動先のDavNodeが存在する場合は、そのDavNodeを削除する(ファイル実体は含まない)。
if (null != dstNode) {
dstNode.setRequestType(EsBulkRequest.BULK_REQUEST_TYPE.DELETE);
adsBulkRequest.add(dstNode);
esBulkRequest.add(dstNode);
setDestinationNodeForRollback(dstNode);
}
}
/**
* Move用の更新/削除リクエストをバルクで行う.<br/>
* 全リクエストの情報をログに出力しているため、大量のリクエストを行う際には要注意.<br/>
* ES用のバルク登録ドキュメント数とADS用のバルク登録ドキュメント数は、同じにすること
* @return バルクレスポンス
*/
public DcBulkResponse move() {
DcBulkResponse response = esMove(this.esBulkRequest);
// ESへのバルクリクエストでエラーが発生した場合はロールバックするためバイナリファイルの削除とADSの更新は行わない
if (response.hasFailures()) {
rollback();
throw DcCoreException.Server.DATA_STORE_UPDATE_ERROR_ROLLBACKED;
}
adsMove(response);
deleteDavFile(this.dstNoeForRollback);
return response;
}
private DcBulkResponse adsMove(DcBulkResponse response) {
// ADSへの書込みは、連続した登録/更新のリクエストをバルクで実行する
// 削除リクエストはリクエストを単体で実行する
// 例)登録→更新→削除→削除→登録→更新→登録→削除 の順で実行した場合
// 1. 登録→更新 をバルクで実行
// 2. 削除を実行
// 3. 削除を実行
// 4. 登録→更新→登録 をバルクで実行
// 5. 削除を実行
// TODO 削除リクエストを複数実行する際は、バルク化する
if (getAds() != null) {
List<DavNode> adsUpdateBulkRequest = new ArrayList<DavNode>();
int i;
for (i = 0; i < esBulkRequest.size(); i++) {
EsBulkRequest esReq = esBulkRequest.get(i);
if (BULK_REQUEST_TYPE.DELETE != esReq.getRequestType()) {
adsUpdateBulkRequest.add(adsBulkRequest.get(i));
} else {
// Davテーブル一括更新
bulkUpdateAds(adsUpdateBulkRequest, response, i);
adsUpdateBulkRequest.clear();
// Davテーブル削除
deleteAds(adsBulkRequest.get(i), -1);
}
}
// Davテーブル一括更新
bulkUpdateAds(adsUpdateBulkRequest, response, i);
}
return response;
}
/**
* moveメソッドでのElasticsearchへのデータの更新を行う. <br />
* ロールバックでもこのメソッドを使用するため、リクエスト情報はパラメータとして受け取る。
* @param esRequest ES用バルク登録ドキュメントリスト
* @return バルクレスポンス
*/
public DcBulkResponse esMove(List<EsBulkRequest> esRequest) {
// マスタ書き込みでエラーが発生したためES更新を不可能とする
prepareDataUpdate(getIndex().getName());
DcBulkResponse response = null;
try {
response = getIndex().bulkRequest(getRoutingId(), esRequest, true);
} catch (EsClientException.EsNoResponseException e) {
throw DcCoreException.Server.ES_RETRY_OVER.params(e.getMessage());
}
return response;
}
/**
* マスターデータを一括更新する.
* @param metaFile 更新データ
* @param response Elasticsearchのバルク更新レスポンス(Ads書込み失敗ログ出力用)
* @param responseIndex Elasticsearchのバルク更新レスポンスのインデックス
*/
private void bulkUpdateAds(List<DavNode> adsRequest, DcBulkResponse response, int responseIndex) {
try {
// Davテーブルを一括更新
if (adsRequest.size() > 0) {
getAds().bulkUpdateDav(getIndex().getName(), adsRequest);
}
} catch (AdsException e) {
DcCoreLog.Server.DATA_STORE_ENTITY_BULK_CREATE_FAIL.params(e.getMessage()).reason(e).writeLog();
// Adsの登録に失敗した場合は、専用のログに書込む
// ESでのバージョン情報を取得するためにesBulkRequestをループさせている
DcBulkItemResponse[] responseItems = response.items();
for (DavNode docHandler : adsRequest) {
// Adsの登録に失敗した場合は、専用のログに書込む
DcBulkItemResponse itemResponse = responseItems[responseIndex++];
String lockKey = LockKeyComposer.fullKeyFromCategoryAndKey(Lock.CATEGORY_DAV,
null, docHandler.getBoxId(), null);
AdsWriteFailureLogInfo loginfo = new AdsWriteFailureLogInfo(
this.getIndex().getName(), "dav", lockKey,
docHandler.getCellId(), docHandler.getId(),
AdsWriteFailureLogInfo.OperationKind.UPDATE, itemResponse.version(),
docHandler.getUpdated());
recordAdsWriteFailureLog(loginfo);
}
}
}
/**
* ESへのバルクリクエスト失敗時に、元の状態に戻すためのリクエストを実行する.
*/
private void rollback() {
log.info("Failed to update data store, then rollback start");
// バルクリクエスト作成
List<DavNode> adsRequest = new ArrayList<DavNode>();
List<EsBulkRequest> esRequest = new ArrayList<EsBulkRequest>();
adsRequest.add(this.srcParentNodeForRollback);
esRequest.add(this.srcParentNodeForRollback);
if (null != this.dstParentNodeForRollback) {
adsRequest.add(this.dstParentNodeForRollback);
esRequest.add(this.dstParentNodeForRollback);
}
if (null != this.srcNodeForRollback) {
adsRequest.add(this.srcNodeForRollback);
esRequest.add(this.srcNodeForRollback);
}
if (null != this.dstNoeForRollback) {
adsRequest.add(this.dstNoeForRollback);
esRequest.add(this.dstNoeForRollback);
}
// ロールバックではElasticsearchのデータのみを戻す
DcBulkResponse bulkResponse = esMove(esRequest);
if (bulkResponse.hasFailures()) {
// ロールバック失敗時はロールバックしようとしたデータの内容を出力する
log.info("rollback was abnormally end.");
outputRollbackRequest("srcParent ", srcParentNodeForRollback);
outputRollbackRequest("dstParent", dstParentNodeForRollback);
outputRollbackRequest("source", srcNodeForRollback);
outputRollbackRequest("destination", dstNoeForRollback);
// ロールバックに失敗
throw DcCoreException.Server.DATA_STORE_UPDATE_ROLLBACK_ERROR;
}
// ロールバックに成功
log.info("rollback was successfully end.");
}
/**
* ロールバック失敗時にデータ補正用のDavNodeデータを出力する.
* @param prefix データ項目名
* @param davNode DavNode
*/
private void outputRollbackRequest(String prefix, DavNode davNode) {
if (null != davNode) {
log.info(String.format("%-11s: %s", prefix, davNode.getSource().toJSONString()));
}
}
/**
* DavNodeのIDをもとにファイルを削除する.
* @param davCmp Davコンポーネント
*/
private void deleteDavFile(DavNode davNode) {
if (null != davNode) {
BinaryDataAccessor accessor = getBinaryDataAccessor();
try {
accessor.delete(davNode.getId());
} catch (BinaryDataAccessException e) {
DcCoreLog.Dav.FILE_DELETE_FAIL.params(davNode.getId()).writeLog();
}
}
}
/**
* バイナリデータのアクセサのインスタンスを生成して返す.
* @return アクセサのインスタンス
*/
protected BinaryDataAccessor getBinaryDataAccessor() {
String unitUserName = getIndex().getName().replace(DcCoreConfig.getEsUnitPrefix() + "_", "");
return new BinaryDataAccessor(DcCoreConfig.getBlobStoreRoot(), unitUserName,
DcCoreConfig.getPhysicalDeleteMode(), DcCoreConfig.getFsyncEnabled());
}
}