package org.codelibs.elasticsearch.taste.rest.handler;
import static org.codelibs.elasticsearch.taste.util.ListenerUtils.on;
import java.security.InvalidParameterException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.codelibs.elasticsearch.taste.TasteConstants;
import org.codelibs.elasticsearch.taste.exception.OperationFailedException;
import org.codelibs.elasticsearch.taste.util.ListenerUtils.OnFailureListener;
import org.codelibs.elasticsearch.taste.util.ListenerUtils.OnResponseListener;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse;
import org.elasticsearch.action.index.IndexRequest.OpType;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException;
import org.elasticsearch.common.xcontent.ToXContent.Params;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.engine.DocumentAlreadyExistsException;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.indices.IndexAlreadyExistsException;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHitField;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.threadpool.ThreadPool;
public class ItemRequestHandler extends DefaultRequestHandler {
public ItemRequestHandler(final Settings settings, final Client client, final ThreadPool pool) {
super(settings, client, pool);
}
public boolean hasItem(final Map<String, Object> requestMap) {
return requestMap.containsKey("item");
}
@Override
public void execute(final Params params,
final RequestHandler.OnErrorListener listener,
final Map<String, Object> requestMap,
final Map<String, Object> paramMap, final RequestHandlerChain chain) {
final String index = params.param(
TasteConstants.REQUEST_PARAM_ITEM_INDEX, params.param("index"));
final String itemType = params.param(
TasteConstants.REQUEST_PARAM_ITEM_TYPE,
TasteConstants.ITEM_TYPE);
final String itemIdField = params.param(
TasteConstants.REQUEST_PARAM_ITEM_ID_FIELD,
TasteConstants.ITEM_ID_FIELD);
final String idField = params.param(
TasteConstants.REQUEST_PARAM_ID_FIELD, "id");
final String timestampField = params.param(
TasteConstants.REQUEST_PARAM_TIMESTAMP_FIELD,
TasteConstants.TIMESTAMP_FIELD);
@SuppressWarnings("unchecked")
final Map<String, Object> itemMap = (Map<String, Object>) requestMap
.get("item");
if (itemMap == null) {
throw new InvalidParameterException("Item is null.");
}
Object systemId = itemMap.get("system_id");
if (systemId == null) {
systemId = itemMap.remove(idField);
if (systemId == null) {
throw new InvalidParameterException("Item ID is null.");
}
itemMap.put("system_id", systemId);
}
try {
final OnResponseListener<SearchResponse> responseListener = response -> {
validateRespose(response);
final String updateType = params.param("update");
final SearchHits hits = response.getHits();
if (hits.getTotalHits() == 0) {
doItemCreation(params, listener, requestMap, paramMap,
itemMap, index, itemType, itemIdField,
timestampField, chain);
} else {
final SearchHit[] searchHits = hits.getHits();
final SearchHitField field = searchHits[0].getFields().get(
itemIdField);
if (field != null) {
final Number itemId = field.getValue();
if (itemId != null) {
if (TasteConstants.TRUE
.equalsIgnoreCase(updateType)
|| TasteConstants.YES
.equalsIgnoreCase(updateType)) {
doItemUpdate(params, listener, requestMap,
paramMap, itemMap, index, itemType,
itemIdField, timestampField,
itemId.longValue(), OpType.INDEX, chain);
} else {
paramMap.put(itemIdField, itemId.longValue());
chain.execute(params, listener, requestMap,
paramMap);
}
return;
}
}
throw new OperationFailedException("Item does not have "
+ itemIdField + ": " + searchHits[0]);
}
};
final OnFailureListener failureListener = t -> {
final List<Throwable> errorList = getErrorList(paramMap);
if (errorList.size() >= maxRetryCount) {
listener.onError(t);
} else {
sleep(t);
errorList.add(t);
doItemIndexExists(params, listener, requestMap, paramMap,
chain);
}
};
client.prepareSearch(index).setTypes(itemType)
.setQuery(QueryBuilders.termQuery("system_id", systemId))
.addField(itemIdField)
.addSort(timestampField, SortOrder.DESC).setSize(1)
.execute(on(responseListener, failureListener));
} catch (final Exception e) {
final List<Throwable> errorList = getErrorList(paramMap);
if (errorList.size() >= maxRetryCount) {
listener.onError(e);
} else {
sleep(e);
errorList.add(e);
fork(() -> execute(params, listener, requestMap, paramMap,
chain));
}
}
}
private void doItemIndexExists(final Params params,
final RequestHandler.OnErrorListener listener,
final Map<String, Object> requestMap,
final Map<String, Object> paramMap, final RequestHandlerChain chain) {
final String index = params.param(
TasteConstants.REQUEST_PARAM_ITEM_INDEX, params.param("index"));
try {
indexCreationLock.lock();
final IndicesExistsResponse indicesExistsResponse = client.admin()
.indices().prepareExists(index).execute().actionGet();
if (indicesExistsResponse.isExists()) {
doItemMappingCreation(params, listener, requestMap, paramMap,
chain);
} else {
doItemIndexCreation(params, listener, requestMap, paramMap,
chain, index);
}
} catch (final Exception e) {
final List<Throwable> errorList = getErrorList(paramMap);
if (errorList.size() >= maxRetryCount) {
listener.onError(e);
} else {
sleep(e);
errorList.add(e);
fork(() -> execute(params, listener, requestMap, paramMap,
chain));
}
} finally {
indexCreationLock.unlock();
}
}
private void doItemIndexCreation(final Params params,
final RequestHandler.OnErrorListener listener,
final Map<String, Object> requestMap,
final Map<String, Object> paramMap,
final RequestHandlerChain chain, final String index) {
try {
final CreateIndexResponse createIndexResponse = client.admin()
.indices().prepareCreate(index).execute().actionGet();
if (createIndexResponse.isAcknowledged()) {
doItemMappingCreation(params, listener, requestMap, paramMap,
chain);
} else {
listener.onError(new OperationFailedException(
"Failed to create " + index));
}
} catch (final IndexAlreadyExistsException e) {
fork(() -> doItemIndexExists(params, listener, requestMap,
paramMap, chain));
} catch (final Exception e) {
final List<Throwable> errorList = getErrorList(paramMap);
if (errorList.size() >= maxRetryCount) {
listener.onError(e);
} else {
sleep(e);
errorList.add(e);
fork(() -> execute(params, listener, requestMap, paramMap,
chain));
}
}
}
private void doItemMappingCreation(final Params params,
final RequestHandler.OnErrorListener listener,
final Map<String, Object> requestMap,
final Map<String, Object> paramMap, final RequestHandlerChain chain) {
final String index = params.param(
TasteConstants.REQUEST_PARAM_ITEM_INDEX, params.param("index"));
final String type = params.param(
TasteConstants.REQUEST_PARAM_ITEM_TYPE,
TasteConstants.ITEM_TYPE);
final String itemIdField = params.param(
TasteConstants.REQUEST_PARAM_ITEM_ID_FIELD,
TasteConstants.ITEM_ID_FIELD);
final String timestampField = params.param(
TasteConstants.REQUEST_PARAM_TIMESTAMP_FIELD,
TasteConstants.TIMESTAMP_FIELD);
try (XContentBuilder jsonBuilder = XContentFactory.jsonBuilder()) {
final ClusterHealthResponse healthResponse = client
.admin()
.cluster()
.prepareHealth(index)
.setWaitForYellowStatus()
.setTimeout(
params.param("timeout",
DEFAULT_HEALTH_REQUEST_TIMEOUT)).execute()
.actionGet();
if (healthResponse.isTimedOut()) {
listener.onError(new OperationFailedException(
"Failed to create index: " + index + "/" + type));
}
final XContentBuilder builder = jsonBuilder//
.startObject()//
.startObject(type)//
.startObject("properties")//
// @timestamp
.startObject(timestampField)//
.field("type", "date")//
.field("format", "date_optional_time")//
.endObject()//
// item_id
.startObject(itemIdField)//
.field("type", "long")//
.endObject()//
// system_id
.startObject("system_id")//
.field("type", "string")//
.field("index", "not_analyzed")//
.endObject()//
.endObject()//
.endObject()//
.endObject();
final PutMappingResponse mappingResponse = client.admin().indices()
.preparePutMapping(index).setType(type).setSource(builder)
.execute().actionGet();
if (mappingResponse.isAcknowledged()) {
fork(() -> execute(params, listener, requestMap, paramMap,
chain));
} else {
listener.onError(new OperationFailedException(
"Failed to create mapping for " + index + "/" + type));
}
} catch (final Exception e) {
listener.onError(e);
}
}
private void doItemCreation(final Params params,
final RequestHandler.OnErrorListener listener,
final Map<String, Object> requestMap,
final Map<String, Object> paramMap,
final Map<String, Object> itemMap, final String index,
final String type, final String itemIdField,
final String timestampField, final RequestHandlerChain chain) {
final OnResponseListener<SearchResponse> responseListener = response -> {
validateRespose(response);
Number currentId = null;
final SearchHits hits = response.getHits();
if (hits.getTotalHits() != 0) {
final SearchHit[] searchHits = hits.getHits();
final SearchHitField field = searchHits[0].getFields().get(
itemIdField);
if (field != null) {
currentId = field.getValue();
}
}
final Long itemId;
if (currentId == null) {
itemId = Long.valueOf(1);
} else {
itemId = Long.valueOf(currentId.longValue() + 1);
}
doItemUpdate(params, listener, requestMap, paramMap, itemMap,
index, type, itemIdField, timestampField, itemId,
OpType.CREATE, chain);
};
final OnFailureListener failureListener = t -> {
final List<Throwable> errorList = getErrorList(paramMap);
if (errorList.size() >= maxRetryCount) {
listener.onError(t);
} else {
sleep(t);
errorList.add(t);
doItemIndexExists(params, listener, requestMap, paramMap,
chain);
}
};
client.prepareSearch(index).setTypes(type)
.setQuery(QueryBuilders.matchAllQuery()).addField(itemIdField)
.addSort(itemIdField, SortOrder.DESC).setSize(1)
.execute(on(responseListener, failureListener));
}
private void doItemUpdate(final Params params,
final RequestHandler.OnErrorListener listener,
final Map<String, Object> requestMap,
final Map<String, Object> paramMap,
final Map<String, Object> itemMap, final String index,
final String type, final String itemIdField,
final String timestampField, final Long itemId,
final OpType opType, final RequestHandlerChain chain) {
itemMap.put(itemIdField, itemId);
itemMap.put(timestampField, new Date());
final OnResponseListener<IndexResponse> responseListener = response -> {
paramMap.put(itemIdField, itemId);
chain.execute(params, listener, requestMap, paramMap);
};
final OnFailureListener failureListener = t -> {
sleep(t);
if (t instanceof DocumentAlreadyExistsException
|| t instanceof EsRejectedExecutionException) {
execute(params, listener, requestMap, paramMap, chain);
} else {
listener.onError(t);
}
};
client.prepareIndex(index, type, itemId.toString()).setSource(itemMap)
.setRefresh(true).setOpType(opType)
.execute(on(responseListener, failureListener));
}
}