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 UserRequestHandler extends DefaultRequestHandler { public UserRequestHandler(final Settings settings, final Client client, final ThreadPool pool) { super(settings, client, pool); } public boolean hasUser(final Map<String, Object> requestMap) { return requestMap.containsKey("user"); } @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_USER_INDEX, params.param("index")); final String userType = params.param( TasteConstants.REQUEST_PARAM_USER_TYPE, TasteConstants.USER_TYPE); final String userIdField = params.param( TasteConstants.REQUEST_PARAM_USER_ID_FIELD, TasteConstants.USER_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> userMap = (Map<String, Object>) requestMap .get("user"); if (userMap == null) { throw new InvalidParameterException("User is null."); } Object systemId = userMap.get("system_id"); if (systemId == null) { systemId = userMap.remove(idField); if (systemId == null) { throw new InvalidParameterException("User ID is null."); } userMap.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) { doUserCreation(params, listener, requestMap, paramMap, userMap, index, userType, userIdField, timestampField, chain); } else { final SearchHit[] searchHits = hits.getHits(); final SearchHitField field = searchHits[0].getFields().get( userIdField); if (field != null) { final Number userId = field.getValue(); if (userId != null) { if (TasteConstants.TRUE .equalsIgnoreCase(updateType) || TasteConstants.YES .equalsIgnoreCase(updateType)) { doUserUpdate(params, listener, requestMap, paramMap, userMap, index, userType, userIdField, timestampField, userId.longValue(), OpType.INDEX, chain); } else { paramMap.put(userIdField, userId.longValue()); chain.execute(params, listener, requestMap, paramMap); } return; } } throw new OperationFailedException("User does not have " + userIdField + ": " + 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); doUserIndexExists(params, listener, requestMap, paramMap, chain); } }; client.prepareSearch(index).setTypes(userType) .setQuery(QueryBuilders.termQuery("system_id", systemId)) .addField(userIdField) .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 doUserIndexExists(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_USER_INDEX, params.param("index")); try { indexCreationLock.lock(); final IndicesExistsResponse indicesExistsResponse = client.admin() .indices().prepareExists(index).execute().actionGet(); if (indicesExistsResponse.isExists()) { doUserMappingCreation(params, listener, requestMap, paramMap, chain); } else { doUserIndexCreation(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 doUserIndexCreation(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()) { doUserMappingCreation(params, listener, requestMap, paramMap, chain); } else { listener.onError(new OperationFailedException( "Failed to create " + index)); } } catch (final IndexAlreadyExistsException e) { fork(() -> doUserIndexExists(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 doUserMappingCreation(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_USER_INDEX, params.param("index")); final String type = params.param( TasteConstants.REQUEST_PARAM_USER_TYPE, TasteConstants.USER_TYPE); final String userIdField = params.param( TasteConstants.REQUEST_PARAM_USER_ID_FIELD, TasteConstants.USER_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()// // user_id .startObject(userIdField)// .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 doUserCreation(final Params params, final RequestHandler.OnErrorListener listener, final Map<String, Object> requestMap, final Map<String, Object> paramMap, final Map<String, Object> userMap, final String index, final String type, final String userIdField, 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( userIdField); if (field != null) { currentId = field.getValue(); } } final Long userId; if (currentId == null) { userId = Long.valueOf(1); } else { userId = Long.valueOf(currentId.longValue() + 1); } doUserUpdate(params, listener, requestMap, paramMap, userMap, index, type, userIdField, timestampField, userId, 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); doUserIndexExists(params, listener, requestMap, paramMap, chain); } }; client.prepareSearch(index).setTypes(type) .setQuery(QueryBuilders.matchAllQuery()).addField(userIdField) .addSort(userIdField, SortOrder.DESC).setSize(1) .execute(on(responseListener, failureListener)); } private void doUserUpdate(final Params params, final RequestHandler.OnErrorListener listener, final Map<String, Object> requestMap, final Map<String, Object> paramMap, final Map<String, Object> userMap, final String index, final String type, final String userIdField, final String timestampField, final Long userId, final OpType opType, final RequestHandlerChain chain) { userMap.put(userIdField, userId); userMap.put(timestampField, new Date()); final OnResponseListener<IndexResponse> responseListener = response -> { paramMap.put(userIdField, userId); chain.execute(params, listener, requestMap, paramMap); }; final OnFailureListener failureListener = t -> { if (t instanceof DocumentAlreadyExistsException || t instanceof EsRejectedExecutionException) { sleep(t); execute(params, listener, requestMap, paramMap, chain); } else { listener.onError(t); } }; client.prepareIndex(index, type, userId.toString()).setSource(userMap) .setRefresh(true).setOpType(opType) .execute(on(responseListener, failureListener)); } }