/**
* 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.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.StringTokenizer;
import org.elasticsearch.action.ActionFuture;
import org.elasticsearch.action.ListenableActionFuture;
import org.elasticsearch.action.WriteConsistencyLevel;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexResponse;
import org.elasticsearch.action.admin.indices.flush.FlushRequest;
import org.elasticsearch.action.admin.indices.flush.FlushResponse;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequestBuilder;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse;
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
import org.elasticsearch.action.admin.indices.refresh.RefreshResponse;
import org.elasticsearch.action.admin.indices.status.IndicesStatusRequestBuilder;
import org.elasticsearch.action.admin.indices.status.IndicesStatusResponse;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequestBuilder;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.deletebyquery.DeleteByQueryResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest.OpType;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.MultiSearchRequest;
import org.elasticsearch.action.search.MultiSearchResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.MappingMetaData;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.collect.ImmutableList;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.fujitsu.dc.common.es.EsBulkRequest;
import com.fujitsu.dc.common.es.EsClient.Event;
import com.fujitsu.dc.common.es.EsClient.EventHandler;
import com.fujitsu.dc.common.es.EsRequestLogInfo;
import com.fujitsu.dc.common.es.response.DcBulkResponse;
import com.fujitsu.dc.common.es.response.DcRefreshResponse;
import com.fujitsu.dc.common.es.response.EsClientException;
import com.fujitsu.dc.common.es.response.EsClientException.EsMultiSearchQueryParseException;
import com.fujitsu.dc.common.es.response.impl.DcBulkResponseImpl;
import com.fujitsu.dc.common.es.response.impl.DcRefreshResponseImpl;
/**
* ElasticSearchのアクセサクラス.
*/
public class InternalEsClient {
private static final int DEFAULT_ES_PORT = 9300;
private TransportClient esTransportClient;
private boolean routingFlag;
/**
* デフォルトコンストラクタ.
*/
protected InternalEsClient() {
}
/**
* コンストラクタ.
* @param cluster クラスタ名
* @param hosts ホスト
*/
protected InternalEsClient(String cluster, String hosts) {
routingFlag = true;
prepareClient(cluster, hosts);
}
/**
* クラスタ名、接続先情報を指定してEsClientのインスタンスを返す.
* 既に生成されているインスタンスは破棄する
* @param cluster クラスタ名
* @param hosts 接続先情報
* @return EsClientのインスタンス
*/
public static InternalEsClient getInstance(String cluster, String hosts) {
return new InternalEsClient(cluster, hosts);
}
/**
* ESとのコネクションを一度明示的に閉じる.
*/
public void closeConnection() {
if (esTransportClient == null) {
return;
}
esTransportClient.close();
esTransportClient = null;
}
private void prepareClient(String clusterName, String hostNames) {
if (esTransportClient != null) {
return;
}
if (clusterName == null || hostNames == null) {
return;
}
Settings st = ImmutableSettings.settingsBuilder()
.put("cluster.name", clusterName)
.put("client.transport.sniff", true)
.build();
ImmutableList<DiscoveryNode> connectedNodes = null;
esTransportClient = new TransportClient(st);
List<EsHost> hostList = parseConfigAndInitializeHostsList(hostNames);
for (EsHost host : hostList) {
esTransportClient.addTransportAddress(new InetSocketTransportAddress(host.getName(), host.getPort()));
connectedNodes = esTransportClient.connectedNodes();
}
if (connectedNodes.isEmpty()) {
throw new EsClientException("Datastore Connection Error.");
}
loggingConnectedNode(connectedNodes);
}
private List<EsHost> parseConfigAndInitializeHostsList(String hostNames) {
List<EsHost> hostList = new ArrayList<EsHost>();
StringTokenizer tokenizer = new StringTokenizer(hostNames, ",");
while (tokenizer.hasMoreTokens()) {
String host = tokenizer.nextToken();
hostList.add(createEsHost(host));
}
return hostList;
}
private EsHost createEsHost(String host) {
EsHost hostInfo = null;
if (hasPortNumber(host)) {
int index = host.indexOf(":");
hostInfo = new EsHost(host.substring(0, index), Integer.parseInt(host.substring(index + 1)));
} else {
hostInfo = new EsHost(host, DEFAULT_ES_PORT);
}
return hostInfo;
}
private boolean hasPortNumber(String host) {
return host.indexOf(":") > 0;
}
private void loggingConnectedNode(ImmutableList<DiscoveryNode> list) {
DiscoveryNode node = list.get(0);
this.fireEvent(Event.connected, node.address().toString());
}
/**
* elasticsearchのノード情報(ホスト名、ポート番号)を保持するコンテナクラス.
*/
private static class EsHost {
private String name;
private int port;
public EsHost(String name, int port) {
this.name = name;
this.port = port;
}
public String getName() {
return name;
}
public int getPort() {
return port;
}
}
static Map<Event, EventHandler> eventHandlerMap = new HashMap<Event, EventHandler>();
/**
* Eventハンドラの登録.
* @param ev イベントの種類
* @param handler ハンドラ
*/
public static void setEventHandler(Event ev, EventHandler handler) {
eventHandlerMap.put(ev, handler);
}
void fireEvent(Event ev, final Object... params) {
this.fireEvent(ev, null, params);
}
void fireEvent(Event ev, EsRequestLogInfo logInfo, final Object... params) {
EventHandler handler = eventHandlerMap.get(ev);
if (handler != null) {
handler.handleEvent(logInfo, params);
}
}
/**
* Clusterの状態取得.
* @return 状態Map
*/
public Map<String, Object> checkHealth() {
ClusterHealthResponse clusterHealth;
clusterHealth = esTransportClient.admin().cluster().health(new ClusterHealthRequest()).actionGet();
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("cluster_name", clusterHealth.getClusterName());
map.put("status", clusterHealth.getStatus().name());
map.put("timed_out", clusterHealth.isTimedOut());
map.put("number_of_nodes", clusterHealth.getNumberOfNodes());
map.put("number_of_data_nodes", clusterHealth.getNumberOfDataNodes());
map.put("active_primary_shards", clusterHealth.getActivePrimaryShards());
map.put("active_shards", clusterHealth.getActiveShards());
map.put("relocating_shards", clusterHealth.getRelocatingShards());
map.put("initializing_shards", clusterHealth.getInitializingShards());
map.put("unassigned_shards", clusterHealth.getUnassignedShards());
return map;
}
/**
* インデックスを作成する.
* @param index インデックス名
* @param mappings マッピング情報
* @return 非同期応答
*/
public ActionFuture<CreateIndexResponse> createIndex(String index, Map<String, JSONObject> mappings) {
this.fireEvent(Event.creatingIndex, index);
CreateIndexRequestBuilder cirb =
new CreateIndexRequestBuilder(esTransportClient.admin().indices()).setIndex(index);
// cjkアナライザ設定
ImmutableSettings.Builder indexSettings = ImmutableSettings.settingsBuilder();
indexSettings.put("analysis.analyzer.default.type", "cjk");
cirb.setSettings(indexSettings);
if (mappings != null) {
for (Map.Entry<String, JSONObject> ent : mappings.entrySet()) {
cirb = cirb.addMapping(ent.getKey(), ent.getValue().toString());
}
}
return cirb.execute();
}
/**
* インデックスを削除する.
* @param index インデックス名
* @return 非同期応答
*/
public ActionFuture<DeleteIndexResponse> deleteIndex(String index) {
DeleteIndexRequest dir = new DeleteIndexRequest(index);
return esTransportClient.admin().indices().delete(dir);
}
/**
* インデックスの設定を更新する.
* @param index インデックス名
* @param settings 更新するインデックス設定
* @return Void
*/
public Void updateIndexSettings(String index, Map<String, String> settings) {
Settings settingsForUpdate = ImmutableSettings.settingsBuilder().put(settings).build();
esTransportClient.admin().indices().prepareUpdateSettings(index).setSettings(settingsForUpdate).execute()
.actionGet();
return null;
}
/**
* Mapping定義を取得する.
* @param index インデックス名
* @param type タイプ名
* @return Mapping定義
*/
public MappingMetaData getMapping(String index, String type) {
ClusterState cs = esTransportClient.admin().cluster().prepareState().
setIndices(index).execute().actionGet().getState();
return cs.getMetaData().index(index).mapping(type);
}
/**
* Mapping定義を更新する.
* @param index インデックス名
* @param type タイプ名
* @param mappings マッピング情報
* @return 非同期応答
*/
public ListenableActionFuture<PutMappingResponse> putMapping(String index,
String type,
Map<String, Object> mappings) {
PutMappingRequestBuilder builder = new PutMappingRequestBuilder(esTransportClient.admin().indices())
.setIndices(index)
.setType(type)
.setSource(mappings);
return builder.execute();
}
/**
* インデックスステータスを取得する.
* @return 非同期応答
*/
public ActionFuture<IndicesStatusResponse> indicesStatus() {
IndicesStatusRequestBuilder cirb =
new IndicesStatusRequestBuilder(esTransportClient.admin().indices());
return cirb.execute();
}
/**
* 非同期でドキュメントを取得.
* @param index インデックス名
* @param type タイプ名
* @param id ドキュメントのID
* @param routingId routingId
* @param realtime リアルタイムモードなら真
* @return 非同期応答
*/
public ActionFuture<GetResponse> asyncGet(String index, String type, String id, String routingId,
boolean realtime) {
GetRequest req = new GetRequest(index, type, id);
if (routingFlag) {
req = req.routing(routingId);
}
req.realtime(realtime);
ActionFuture<GetResponse> ret = esTransportClient.get(req);
this.fireEvent(Event.afterRequest, index, type, id, null, "Get");
return ret;
}
/**
* 非同期でドキュメントを検索.
* @param index インデックス名
* @param type タイプ名
* @param routingId routingId
* @param builder クエリ情報
* @return 非同期応答
*/
public ActionFuture<SearchResponse> asyncSearch(
String index,
String type,
String routingId,
SearchSourceBuilder builder) {
SearchRequest req = new SearchRequest(index).types(type).searchType(SearchType.DEFAULT).source(builder);
if (routingFlag) {
req = req.routing(routingId);
}
ActionFuture<SearchResponse> ret = esTransportClient.search(req);
this.fireEvent(Event.afterRequest, index, type, null,
new String(builder.buildAsBytes().toBytes()), "Search");
return ret;
}
/**
* 非同期でドキュメントを検索.
* @param index インデックス名
* @param type タイプ名
* @param routingId routingId
* @param query クエリ情報
* @return 非同期応答
*/
public ActionFuture<SearchResponse> asyncSearch(
String index,
String type,
String routingId,
Map<String, Object> query) {
SearchRequest req = new SearchRequest(index).types(type).searchType(SearchType.DEFAULT);
if (query != null) {
req.source(query);
}
if (routingFlag) {
req = req.routing(routingId);
}
ActionFuture<SearchResponse> ret = esTransportClient.search(req);
this.fireEvent(Event.afterRequest, index, type, null, JSONObject.toJSONString(query), "Search");
return ret;
}
/**
* 非同期でドキュメントを検索. <br />
* Queryの指定方法をMapで直接記述せずにQueryBuilderにするため、非推奨とする.
* @param index インデックス名
* @param routingId routingId
* @param query クエリ情報
* @return 非同期応答
*/
public ActionFuture<SearchResponse> asyncSearch(
String index,
String routingId,
Map<String, Object> query) {
SearchRequest req = new SearchRequest(index).searchType(SearchType.DEFAULT);
if (query != null) {
req.source(query);
}
if (routingFlag) {
req = req.routing(routingId);
}
ActionFuture<SearchResponse> ret = esTransportClient.search(req);
this.fireEvent(Event.afterRequest, index, null, null, JSONObject.toJSONString(query), "Search");
return ret;
}
/**
* 非同期でドキュメントを検索.
* @param index インデックス名
* @param routingId routingId
* @param query クエリ情報
* @return 非同期応答
*/
public ActionFuture<SearchResponse> asyncSearch(
String index,
String routingId,
QueryBuilder query) {
SearchRequest req = new SearchRequest(index).searchType(SearchType.DEFAULT);
String queryString = "null";
if (query != null) {
req.source(new SearchSourceBuilder().query(query));
queryString = query.buildAsBytes().toUtf8();
}
if (routingFlag) {
req = req.routing(routingId);
}
ActionFuture<SearchResponse> ret = esTransportClient.search(req);
this.fireEvent(Event.afterRequest, index, null, null, queryString, "Search");
return ret;
}
/**
* 非同期でインデックスに対してドキュメントをマルチ検索.
* 存在しないインデックスに対して本メソッドを使用すると、TransportSerializationExceptionがスローされるので注意すること
* @param index インデックス名
* @param routingId routingId
* @param queryList マルチ検索用のクエリ情報リスト
* @return 非同期応答
*/
public ActionFuture<MultiSearchResponse> asyncMultiSearch(
String index,
String routingId,
List<Map<String, Object>> queryList) {
return this.asyncMultiSearch(index, null, routingId, queryList);
}
/**
* 非同期でドキュメントをマルチ検索.
* 存在しないインデックスに対して本メソッドを使用すると、TransportSerializationExceptionがスローされるので注意すること
* @param index インデックス名
* @param type タイプ名
* @param routingId routingId
* @param queryList マルチ検索用のクエリ情報リスト
* @return 非同期応答
*/
public ActionFuture<MultiSearchResponse> asyncMultiSearch(
String index,
String type,
String routingId,
List<Map<String, Object>> queryList) {
MultiSearchRequest mrequest = new MultiSearchRequest();
if (queryList == null || queryList.size() == 0) {
throw new EsMultiSearchQueryParseException();
}
for (Map<String, Object> query : queryList) {
SearchRequest req = new SearchRequest(index).searchType(SearchType.DEFAULT);
if (type != null) {
req.types(type);
}
// クエリ指定なしの場合はタイプに対する全件検索を行う
if (query != null) {
req.source(query);
}
if (routingFlag) {
req = req.routing(routingId);
}
mrequest.add(req);
}
ActionFuture<MultiSearchResponse> ret = esTransportClient.multiSearch(mrequest);
this.fireEvent(Event.afterRequest, index, type, null, JSONArray.toJSONString(queryList), "MultiSearch");
return ret;
}
private static final int SCROLL_SEARCH_KEEP_ALIVE_TIME = 1000 * 60 * 5;
/**
* クエリを指定してスクロールサーチを実行する.
* @param index インデックス名
* @param type タイプ名
* @param query 検索クエリ
* @return 非同期応答
*/
public ActionFuture<SearchResponse> asyncScrollSearch(String index, String type, Map<String, Object> query) {
SearchRequest req = new SearchRequest(index)
.searchType(SearchType.SCAN)
.scroll(new TimeValue(SCROLL_SEARCH_KEEP_ALIVE_TIME));
if (type != null) {
req.types(type);
}
if (query != null) {
req.source(query);
}
ActionFuture<SearchResponse> ret = esTransportClient.search(req);
return ret;
}
/**
* スクロールIDを指定してスクロールサーチを継続する.
* @param scrollId スクロールID
* @return 非同期応答
*/
public ActionFuture<SearchResponse> asyncScrollSearch(String scrollId) {
ActionFuture<SearchResponse> ret = esTransportClient.prepareSearchScroll(scrollId)
.setScroll(new TimeValue(SCROLL_SEARCH_KEEP_ALIVE_TIME))
.execute();
return ret;
}
/**
* 非同期でドキュメントを検索.
* @param index インデックス名
* @param query クエリ情報
* @return 非同期応答
*/
public ActionFuture<SearchResponse> asyncSearch(String index, Map<String, Object> query) {
SearchRequest req = new SearchRequest(index).searchType(SearchType.DEFAULT);
if (query != null) {
req.source(query);
}
ActionFuture<SearchResponse> ret = esTransportClient.search(req);
this.fireEvent(Event.afterRequest, index, null, null, JSONObject.toJSONString(query), "Search");
return ret;
}
/**
* 非同期でドキュメントを登録する.
* @param index インデックス名
* @param type タイプ名
* @param id ドキュメントのid
* @param routingId routingId
* @param data データ
* @param opType 操作タイプ
* @param version version番号
* @return 非同期応答
*/
public ActionFuture<IndexResponse> asyncIndex(String index,
String type,
String id,
String routingId,
Map<String, Object> data,
OpType opType,
long version) {
IndexRequestBuilder req = esTransportClient.prepareIndex(index, type, id).setSource(data).setOpType(opType)
.setConsistencyLevel(WriteConsistencyLevel.DEFAULT).setRefresh(true);
if (routingFlag) {
req = req.setRouting(routingId);
}
if (version > -1) {
req.setVersion(version);
}
ActionFuture<IndexResponse> ret = req.execute();
EsRequestLogInfo logInfo = new EsRequestLogInfo(index, type, id, routingId, data, opType.toString(),
version);
this.fireEvent(Event.afterCreate, logInfo);
return ret;
}
/**
* 非同期でversionつきでdocumentを削除します.
* @param index インデックス名
* @param type タイプ名
* @param id Document id to delete
* @param routingId routingId
* @param version The version of the document to delete
* @return 非同期応答
*/
public ActionFuture<DeleteResponse> asyncDelete(String index, String type,
String id, String routingId, long version) {
DeleteRequestBuilder req = esTransportClient.prepareDelete(index, type, id)
.setRefresh(true);
if (routingFlag) {
req = req.setRouting(routingId);
}
if (version > -1) {
req.setVersion(version);
}
ActionFuture<DeleteResponse> ret = req.execute();
this.fireEvent(Event.afterRequest, index, type, id, null, "Delete");
return ret;
}
/**
* バルクでドキュメントを登録/更新/削除.
* @param index インデックス名
* @param routingId routingId
* @param datas バルクドキュメント
* @param isWriteLog リクエスト情報のログ出力有無
* @return ES応答
*/
@SuppressWarnings("unchecked")
public BulkResponse bulkRequest(String index, String routingId, List<EsBulkRequest> datas, boolean isWriteLog) {
BulkRequestBuilder bulkRequest = esTransportClient.prepareBulk();
List<Map<String, Object>> bulkList = new ArrayList<Map<String, Object>>();
for (EsBulkRequest data : datas) {
if (EsBulkRequest.BULK_REQUEST_TYPE.DELETE == data.getRequestType()) {
bulkRequest.add(createDeleteRequest(index, routingId, data));
} else {
bulkRequest.add(createIndexRequest(index, routingId, data));
}
JSONObject logData = new JSONObject();
logData.put("reqType", data.getRequestType().toString());
logData.put("type", data.getType());
logData.put("id", data.getId());
logData.put("source", data.getSource());
bulkList.add(logData);
}
Map<String, Object> debug = new HashMap<String, Object>();
debug.put("bulk", bulkList);
BulkResponse ret = bulkRequest.setRefresh(true).execute().actionGet();
if (isWriteLog) {
this.fireEvent(Event.afterRequest, index, "none", "none", debug, "bulkRequest");
}
return ret;
}
/**
* バルクリクエストのINDEXリクエストを作成する.
* @param index インデックス名
* @param routingId ルーティングID
* @param data バルクドキュメント情報
* @return 作成したINDEXリクエスト
*/
private IndexRequestBuilder createIndexRequest(String index, String routingId, EsBulkRequest data) {
IndexRequestBuilder request = esTransportClient.
prepareIndex(index, data.getType(), data.getId()).setSource(data.getSource());
if (routingFlag) {
request = request.setRouting(routingId);
}
return request;
}
/**
* バルクリクエストのDELETEリクエストを作成する.
* @param index インデックス名
* @param routingId ルーティングID
* @param data バルクドキュメント情報
* @return 作成したDELETEリクエスト
*/
private DeleteRequestBuilder createDeleteRequest(String index, String routingId, EsBulkRequest data) {
DeleteRequestBuilder request = esTransportClient.prepareDelete(index, data.getType(), data.getId());
if (routingFlag) {
request = request.setRouting(routingId);
}
return request;
}
/**
* ルーティングIDに関係なくバルクでドキュメントを登録.
* @param index インデックス名
* @param bulkMap バルクドキュメント
* @return ES応答
*/
public DcBulkResponse asyncBulkCreate(
String index, Map<String, List<EsBulkRequest>> bulkMap) {
BulkRequestBuilder bulkRequest = esTransportClient.prepareBulk();
// ルーティングIDごとにバルク登録を行うと効率が悪いため、引数で渡されたEsBulkRequestは全て一括登録する。
// また、バルク登録後にactionGet()すると同期実行となるため、ここでは実行しない。
// このため、execute()のレスポンスを返却し、呼び出し側でactionGet()してからレスポンスチェック、リフレッシュすること。
for (Entry<String, List<EsBulkRequest>> ents : bulkMap.entrySet()) {
for (EsBulkRequest data : ents.getValue()) {
IndexRequestBuilder req = esTransportClient.
prepareIndex(index, data.getType(), data.getId()).setSource(data.getSource());
if (routingFlag) {
req = req.setRouting(ents.getKey());
}
bulkRequest.add(req);
}
}
DcBulkResponse response = DcBulkResponseImpl.getInstance(bulkRequest.execute().actionGet());
return response;
}
/**
* 引数で指定されたインデックスに対してrefreshする.
* @param index インデックス名
* @return レスポンス
*/
public DcRefreshResponse refresh(String index) {
RefreshResponse response = esTransportClient.admin().indices()
.refresh(new RefreshRequest(index)).actionGet();
return DcRefreshResponseImpl.getInstance(response);
}
/**
* 指定されたクエリを使用してデータの削除を行う.
* @param index 削除対象のインデックス
* @param deleteQuery 削除対象を指定するクエリ
* @return ES応答
*/
public DeleteByQueryResponse deleteByQuery(String index, QueryBuilder deleteQuery) {
DeleteByQueryResponse response = esTransportClient.prepareDeleteByQuery(index)
.setQuery(deleteQuery).execute().actionGet();
return response;
}
/**
* flushを行う.
* @param index flush対象のindex名
* @return 非同期応答
*/
public ActionFuture<FlushResponse> flushTransLog(String index) {
ActionFuture<FlushResponse> ret = esTransportClient.admin().indices().flush(new FlushRequest(index));
this.fireEvent(Event.afterRequest, index, null, null, null, "Flush");
return ret;
}
}